Mit Async-Funktionen können Sie versprechenbasierten Code so schreiben, als wäre er synchron.
In Chrome, Edge, Firefox und Safari sind asynchrone Funktionen standardmäßig aktiviert und sind wirklich großartig. 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 „klug“ und besser lesbar.
So funktionieren asynchrone Funktionen:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
Wenn Sie das Schlüsselwort async
vor einer Funktionsdefinition einsetzen, können Sie innerhalb der Funktion await
verwenden. Wenn du ein Promise mit await
festlegst, wird die Funktion nicht blockierend pausiert, bis sich das Promise erfüllt. Wenn sich das Versprechen erfüllt, erhalten Sie den Wert zurück. Wenn das Versprechen abgelehnt wird, wird der abgelehnte Wert ausgegeben.
Unterstützte Browser
Beispiel: Protokollieren eines Abrufs
Angenommen, Sie möchten eine URL abrufen und die Antwort als Text protokollieren. So sieht es mithilfe von Versprechen aus:
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
Bei Verwendung asynchroner Funktionen ist das Gleiche dasselbe:
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 identisch, aber alle Callbacks sind verschwunden. Dies erleichtert das 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, was die asynchrone Funktion zurückgibt, aufgelöst oder lehnt es ab, je nachdem, was die asynchrone Funktion auslöst. Also bei:
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
Der Aufruf von hello()
gibt ein Versprechen zurück, das mit "world"
erfüllt wird.
async function foo() {
await wait(500);
throw Error('bar');
}
...Der Aufruf von foo()
gibt ein Promise zurück, das mit Error('bar')
ablehnt.
Beispiel: Antwort streamen
Der Vorteil asynchroner Funktionen steigt 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);
});
});
}
Jake, der „Versprechen“ von Archibald. Sehen Sie, wie ich processResult()
in sich selbst aufrufe, um eine asynchrone Schleife einzurichten. Durch das Schreiben
fühlte ich mich sehr intelligent. Aber wie die meisten "intelligenten" Codes muss man ihn jahrelang anstarren, um herauszufinden, was er tut, wie eines dieser magischen Augenbilder aus den 90er Jahren.
Versuchen wir das 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;
}
Die intelligenten Funktionen sind verschwunden. Die asynchrone Schleife, bei der ich mich so einsam gefühlt hat,
wird durch eine vertrauenswürdige, langweilige Schleife ersetzt. Viel besser. In Zukunft erhalten Sie asynchrone Iterationen, die die while
-Schleife durch eine For-of-Schleife ersetzen und sie noch übersichtlicher machen.
Syntax anderer asynchroner Funktionen
Ich habe Ihnen bereits async function() {}
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 Verwendung von sequenziellen Anzeigen.
Obwohl Sie Code schreiben, der synchron aussieht, achten Sie darauf, dass Sie nicht die Gelegenheit verpassen, parallele Aufgaben zu erledigen.
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
Der obige Vorgang 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!';
}
Der obige Vorgang dauert 500 ms, da beide Wartezeiten gleichzeitig erfolgen. Sehen wir uns ein Beispiel aus der Praxis an.
Beispiel: Abrufe der Reihe nach ausgeben
Angenommen, Sie möchten eine Reihe von URLs abrufen und so schnell wie möglich in der richtigen Reihenfolge 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 intelligent, dass Sie ohne sie besser auskommen würden.
Wenn Sie die oben genannte Funktion jedoch in eine asynchrone Funktion umwandeln, ist es verlockend, zu sequenziell zu gehen:
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 Browserunterstützung: Generatoren
Wenn Ihre Anzeigen auf Browser ausgerichtet sind, die Generatoren unterstützen (die die neueste Version aller gängigen Browser enthalten), können Sie eine Art Asynchrone Polyfill-Funktionen verwenden.
Babel übernimmt das für Sie. Hier finden Sie ein Beispiel über die Babel-REPL
- sehen Sie, wie ähnlich der transpilierte Code ist. 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 Babels Polyfill selbst verwenden. Anstelle von:
async function slowEcho(val) {
await wait(1000);
return val;
}
...fügen Sie den Polyfill ein 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 das Ganze gleich.
Problemumgehung: Regenerator
Bei älteren Browsern kann Babel auch Generatoren transpilieren. So können Sie asynchrone Funktionen bis hin zu IE8 verwenden. 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.
Alles asynchron!
Sobald asynchrone Funktionen in allen Browsern verfügbar sind, sollten Sie sie in jeder Funktion verwenden, die vielversprechend ist. Sie machen nicht nur Ihren Code ordentlicher, sondern sorgen auch dafür, dass diese Funktion immer ein Promise zurückgibt.
Ich habe mich schon 2014 so sehr auf asynchrone Funktionen gefreut und es ist toll, dass sie im echten Leben in Browsern landen. Hoppla!