Mit Async-Funktionen können Sie Promise-basierten Code wie synchron schreiben.
Asynchrone Funktionen sind in Chrome, Edge, Firefox und Safari standardmäßig aktiviert und sie sind ausgesprochen praktisch. Sie ermöglichen es Ihnen, Promise-basierten Code so zu schreiben, als wäre er synchron, aber ohne den Hauptthread zu blockieren. Sie machen Ihren asynchronen Code weniger „clever“ und lesbarer.
Asynchrone Funktionen funktionieren wie folgt:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
Wenn Sie das Schlüsselwort async
vor einer Funktionsdefinition verwenden, können Sie await
innerhalb der Funktion verwenden. Wenn du ein Promise mit await
aktivierst, wird die Funktion auf nicht blockierende Weise pausiert, bis das Versprechen gleich bleibt. Wenn das Versprechen erfüllt, erhalten Sie den Wert zurück. Wenn das Promise ablehnt, wird der abgelehnte Wert ausgelöst.
Unterstützte Browser
Beispiel: Logging eines Abrufs
Angenommen, Sie möchten eine URL abrufen und die Antwort als Text protokollieren. So sieht das mit Versprechen aus:
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
Gleiches gilt für asynchrone Funktionen:
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
} catch (err) {
console.log('fetch failed', err);
}
}
Die Anzahl der Zeilen ist gleich, aber alle Callbacks sind weg. Dadurch ist es leichter zu lesen, insbesondere für diejenigen, die mit Versprechen nicht vertraut sind.
Asynchrone Rückgabewerte
Asynchrone Funktionen geben immer ein Promise zurück, unabhängig davon, ob du await
verwendest oder nicht. Dieses Versprechen wird mit dem aufgelöst, was die asynchrone Funktion zurückgibt, bzw. lehnt mit dem ab, was die asynchrone Funktion auslöst. Mit:
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
...das Aufrufen von hello()
gibt ein Versprechen zurück, das mit "world"
erfüllt wird.
async function foo() {
await wait(500);
throw Error('bar');
}
...das Aufrufen von foo()
gibt ein Versprechen zurück, das mit Error('bar')
abgelehnt wird.
Beispiel: Streaming einer Antwort
Die Vorteile asynchroner Funktionen zeigen sich in komplexeren Beispielen. Angenommen, Sie möchten eine Antwort streamen, während Sie die Blöcke abmelden, und die endgültige Größe zurückgeben.
Hier ist es mit Versprechen:
function getResponseSize(url) {
return fetch(url).then((response) => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
});
});
}
Schau mal her, Jake, der „Schweiger der Versprechen“ Archibald. Sehen Sie, wie ich processResult()
selbst aufrufe, um eine asynchrone Schleife einzurichten? Sehr schlau beim Schreiben. Aber wie die meisten „smarten“ Codes muss man ihn ewig anstarren, um herauszufinden, was er tut, wie z. B. eines dieser Bilder mit magischen Augen aus den 90ern.
Versuchen wir es noch einmal mit asynchronen Funktionen:
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
Alle „Klugen“ sind weg. Die asynchrone Schleife, bei der ich mich so selbstgemacht gefühlt hat, wird durch eine vertrauenswürdige, langweilige Schleife ersetzt. Viel besser. In Zukunft werden asynchrone Iterationen eingeführt, die die while
-Schleife durch eine for-of-Schleife ersetzen und somit noch übersichtlicher werden.
Andere Syntax für asynchrone Funktionen
Ich habe Ihnen async function() {}
bereits gezeigt, aber das Schlüsselwort async
kann mit einer anderen Funktionssyntax verwendet werden:
Pfeilfunktionen
// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
Objektmethoden
const storage = {
async getAvatar(name) {
const cache = await caches.open('avatars');
return cache.match(`/avatars/${name}.jpg`);
}
};
storage.getAvatar('jaffathecake').then(…);
Klassenmethoden
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
Vorsicht! Vermeiden Sie die zu sequenziellen
Auch wenn Sie synchronen Code schreiben, sollten Sie nicht die Gelegenheit verpassen, Dinge parallel auszuführen.
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
Dies dauert 1.000 ms, während:
async function parallel() {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in parallel.
await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
return 'done!';
}
Die Ausführung des obigen Vorgangs dauert 500 ms, da beide Wartezeiten gleichzeitig stattfinden. Sehen wir uns ein Beispiel aus der Praxis an.
Beispiel: Abrufe der Reihe nach ausgeben
Angenommen, Sie möchten so schnell wie möglich und in der richtigen Reihenfolge eine Reihe von URLs abrufen und protokollieren.
Tief durchatmen – so sieht das mit Versprechen aus:
function markHandled(promise) {
promise.catch(() => {});
return promise;
}
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map((url) => {
return markHandled(fetch(url).then((response) => response.text()));
});
// log them in order
return textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then((text) => console.log(text));
}, Promise.resolve());
}
Ja, das stimmt. Ich verwende reduce
, um eine Reihe von Versprechen zu verketten. Ich bin so schlau. Aber diese Programmierung ist so clever, ohne dass Sie besser dran sind.
Wenn Sie den obigen Befehl in eine asynchrone Funktion umwandeln, ist es jedoch verlockend, zu sequenziell:
async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
function markHandled(...promises) { Promise.allSettled(promises); } async function logInOrder(urls) { // fetch all the URLs in parallel const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); }); markHandled(...textPromises); // log them in sequence for (const textPromise of textPromises) { console.log(await textPromise); } }
Behelfslösung für Browsersupport: Generatoren
Wenn Sie Browser auf Browser ansprechen, die Generatoren unterstützen (was die neueste Version aller gängigen Browser enthält), können Sie asynchrone Polyfill-Funktionen verwenden.
Babel erledigt das für Sie, hier ein Beispiel über die Babel-REPL
- Diese Transformation ist Teil der Babel-Voreinstellung es2017.
Ich empfehle den Transpiler-Ansatz, da Sie ihn einfach ausschalten können, sobald Ihre Zielbrowser asynchrone Funktionen unterstützen. Wenn Sie jedoch wirklich keinen Transpiler verwenden möchten, können Sie Babel-Polyfill selbst verwenden. Anstelle von:
async function slowEcho(val) {
await wait(1000);
return val;
}
...fügen Sie Polyfill hinzu und schreiben Sie:
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
Sie müssen einen Generator (function*
) an createAsyncFunction
übergeben und yield
anstelle von await
verwenden. Ansonsten funktioniert alles gleich.
Problemumgehung: Regenerator
Babel kann auch Generatoren transpilieren, sodass Sie asynchrone Funktionen bis hin zu IE8 verwenden können. Dazu benötigen Sie die Babel-Voreinstellung es2017 und die Voreinstellung es2015.
Die Ausgabe ist nicht so schön, also achten Sie auf Code-Bloat.
Synchronisieren Sie alle Elemente.
Sobald asynchrone Funktionen in allen Browsern verfügbar sind, verwenden Sie sie in jeder Versprechungsfunktion, die ein Versprechen zurückgeben. Sie sorgen nicht nur dafür, dass Ihr Code übersichtlicher wird, sondern sorgt auch dafür, dass die Funktion immer ein Versprechen zurückgibt.
Asynchrone Funktionen haben mich 2014 wirklich begeistert und es ist toll, zu sehen, wie sie in Browsern landen. Wow!