Zoeken…
Invoering
async
en await
bouwen bovenop beloften en generatoren om asynchrone acties inline uit te drukken. Dit maakt asynchrone of callback-code veel eenvoudiger te onderhouden.
Functies met het async
trefwoord geven een Promise
terug en kunnen met die syntax worden opgeroepen.
Binnen een async function
het sleutelwoord await
op elke Promise
worden toegepast en zal het hele lichaam van de functie na het await
worden uitgevoerd nadat de belofte is opgelost.
Syntaxis
- async functie foo () {
...
wacht op asyncCall ()
} - async-functie () {...}
- async () => {...}
- (async () => {
const data = await asyncCall ()
console.log (data)}) ()
Opmerkingen
Async-functies zijn een syntactische suiker boven beloftes en generatoren. Ze helpen u om uw code leesbaarder, onderhoudbaarder te maken, gemakkelijker om fouten op te vangen en met minder inspringingsniveaus.
Invoering
Een functie die als async
is gedefinieerd, is een functie die asynchrone acties kan uitvoeren, maar er toch synchroon uitziet. De manier waarop het is gedaan, is het await
sleutelwoord om de functie uit te stellen terwijl het wacht op een belofte om op te lossen of te weigeren.
Opmerking: Async-functies zijn een voorstel van Stage 4 ("Finished") op schema dat moet worden opgenomen in de ECMAScript 2017-standaard.
Bijvoorbeeld met behulp van de op belofte gebaseerde Fetch API :
async function getJSON(url) {
try {
const response = await fetch(url);
return await response.json();
}
catch (err) {
// Rejections in the promise will get thrown here
console.error(err.message);
}
}
Een async-functie retourneert altijd een belofte zelf, zodat u deze in andere asynchrone functies kunt gebruiken.
Pijl functie stijl
const getJSON = async url => {
const response = await fetch(url);
return await response.json();
}
Minder inspringen
Met beloften:
function doTheThing() {
return doOneThing()
.then(doAnother)
.then(doSomeMore)
.catch(handleErrors)
}
Met async-functies:
async function doTheThing() {
try {
const one = await doOneThing();
const another = await doAnother(one);
return await doSomeMore(another);
} catch (err) {
handleErrors(err);
}
}
Merk op hoe het rendement onderaan staat, en niet bovenaan, en u de native foutafhandelingsmechanismen van de taal gebruikt ( try/catch
).
Wachten en voorrang van operator
U moet rekening houden met de prioriteit van de operator bij het gebruik van await
trefwoord.
Stel je voor dat we een asynchrone functie hebben die een andere asynchrone functie getUnicorn()
, getUnicorn()
die een belofte retourneert die wordt getUnicorn()
in een instantie van klasse Unicorn
. Nu willen we de grootte van de eenhoorn krijgen met de methode getSize()
van die klasse.
Bekijk de volgende code:
async function myAsyncFunction() {
await getUnicorn().getSize();
}
Op het eerste gezicht lijkt het geldig, maar dat is het niet. Vanwege operatorprioriteit is dit gelijk aan het volgende:
async function myAsyncFunction() {
await (getUnicorn().getSize());
}
Hier proberen we de methode getSize()
van het Promise-object aan te roepen, wat niet is wat we willen.
In plaats daarvan moeten we haakjes gebruiken om aan te geven dat we eerst willen wachten op de eenhoorn en vervolgens de methode getSize()
van het resultaat aanroepen:
async function asyncFunction() {
(await getUnicorn()).getSize();
}
Natuurlijk. de vorige versie zou in sommige gevallen geldig kunnen zijn, bijvoorbeeld als de functie getUnicorn()
synchroon was, maar de methode getSize()
asynchroon was.
Async-functies in vergelijking met Beloften
async
functies vervangen het Promise
type niet; ze voegen taalzoekwoorden toe die beloftes gemakkelijker maken om te bellen. Ze zijn uitwisselbaar:
async function doAsyncThing() { ... }
function doPromiseThing(input) { return new Promise((r, x) => ...); }
// Call with promise syntax
doAsyncThing()
.then(a => doPromiseThing(a))
.then(b => ...)
.catch(ex => ...);
// Call with await syntax
try {
const a = await doAsyncThing();
const b = await doPromiseThing(a);
...
}
catch(ex) { ... }
Elke functie die ketens van beloften gebruikt, kan worden herschreven met await
:
function newUnicorn() {
return fetch('unicorn.json') // fetch unicorn.json from server
.then(responseCurrent => responseCurrent.json()) // parse the response as JSON
.then(unicorn =>
fetch('new/unicorn', { // send a request to 'new/unicorn'
method: 'post', // using the POST method
body: JSON.stringify({unicorn}) // pass the unicorn to the request body
})
)
.then(responseNew => responseNew.json())
.then(json => json.success) // return success property of response
.catch(err => console.log('Error creating unicorn:', err));
}
De functie kan als volgt worden herschreven met async
/ await
:
async function newUnicorn() {
try {
const responseCurrent = await fetch('unicorn.json'); // fetch unicorn.json from server
const unicorn = await responseCurrent.json(); // parse the response as JSON
const responseNew = await fetch('new/unicorn', { // send a request to 'new/unicorn'
method: 'post', // using the POST method
body: JSON.stringify({unicorn}) // pass the unicorn to the request body
});
const json = await responseNew.json();
return json.success // return success property of response
} catch (err) {
console.log('Error creating unicorn:', err);
}
}
Deze async
variant van newUnicorn()
lijkt een terugkeer Promise
, maar eigenlijk waren er meerdere await
zoekwoorden. Elke beloofde een Promise
, dus eigenlijk hadden we een verzameling beloften in plaats van een ketting.
In feite kunnen we het beschouwen als een function*
-generator, met elk await
op een yield new Promise
. De resultaten van elke belofte zijn echter nodig door de volgende om de functie voort te zetten. Dit is de reden waarom het extra sleutelwoord async
nodig is voor de functie (evenals het sleutelwoord await
bij het aanroepen van de beloften) omdat het Javascript vertelt om automatisch een waarnemer voor deze iteratie te creëren. De Promise
geretourneerd door de async function newUnicorn()
wanneer deze iteratie is voltooid.
In de praktijk hoeft u dat niet in overweging te nemen; await
verbergt de belofte en async
verbergt de generator iteratie.
U kunt async
functies aanroepen alsof het beloften zijn en await
elke belofte of elke async
functie. U hoeft niet te await
een async-functie, net zoals u een belofte kunt uitvoeren zonder een .then()
.
U kunt ook een async
IIFE gebruiken als u die code onmiddellijk wilt uitvoeren:
(async () => {
await makeCoffee()
console.log('coffee is ready!')
})()
Wacht met async
Wanneer u async in loops gebruikt, kunt u enkele van deze problemen tegenkomen.
Als je gewoon probeert await inside forEach
, geeft dit een Unexpected token
forEach
.
(async() => {
data = [1, 2, 3, 4, 5];
data.forEach(e => {
const i = await somePromiseFn(e);
console.log(i);
});
})();
Dit komt doordat je de pijl ten onrechte als een blok hebt gezien. Het await
is in de context van de callback-functie, die niet async
.
De tolk beschermt ons tegen het maken van de bovenstaande fout, maar als u async
toevoegt aan de voor forEach
callback worden er geen fouten gegenereerd. Je denkt misschien dat dit het probleem oplost, maar het zal niet werken zoals verwacht.
Voorbeeld:
(async() => {
data = [1, 2, 3, 4, 5];
data.forEach(async(e) => {
const i = await somePromiseFn(e);
console.log(i);
});
console.log('this will print first');
})();
Dit gebeurt omdat de callback async-functie alleen zichzelf kan pauzeren, niet de ouder-async-functie.
Je zou een asyncForEach-functie kunnen schrijven die een belofte retourneert en dan kun je zoiets
await asyncForEach(async (e) => await somePromiseFn(e), data )
In principe retourneer je een belofte die oplost wanneer alle callbacks worden afgewacht en gedaan. Maar er zijn betere manieren om dit te doen, en dat is gewoon een lus gebruiken.
U kunt gebruik maken van een for-of
lus of een for/while
loop, maakt het eigenlijk niet uit welke je kiest.
(async() => {
data = [1, 2, 3, 4, 5];
for (let e of data) {
const i = await somePromiseFn(e);
console.log(i);
}
console.log('this will print last');
})();
Maar er is nog een vangst. Deze oplossing wacht tot elk gesprek naar somePromiseFn
is voltooid voordat het volgende wordt somePromiseFn
.
Dit is geweldig als u daadwerkelijk wilt dat uw somePromiseFn
aanroepen worden uitgevoerd in volgorde, maar als u wilt dat ze tegelijkertijd worden uitgevoerd, moet u await
op Promise.all
.
(async() => {
data = [1, 2, 3, 4, 5];
const p = await Promise.all(data.map(async(e) => await somePromiseFn(e)));
console.log(...p);
})();
Promise.all
ontvangt een reeks beloften als enige parameter en retourneert een belofte. Wanneer alle beloften in de array zijn opgelost, wordt de geretourneerde belofte ook opgelost. We await
op die belofte en wanneer deze is opgelost, zijn al onze waarden beschikbaar.
De bovenstaande voorbeelden zijn volledig uitvoerbaar. De somePromiseFn
functie kan worden gemaakt als een async echo-functie met een time-out. Je kunt de voorbeelden in de babel-repl uitproberen met tenminste de stage-3
preset en naar de output kijken.
function somePromiseFn(n) {
return new Promise((res, rej) => {
setTimeout(() => res(n), 250);
});
}
Gelijktijdige asynchrone (parallelle) bewerkingen
Vaak wilt u asynchrone bewerkingen parallel uitvoeren. Er is een directe syntaxis die dit ondersteunt in het async
/ await
voorstel, maar omdat await
wacht op een belofte, kun je meerdere beloften samen in Promise.all
om op ze te wachten:
// Not in parallel
async function getFriendPosts(user) {
friendIds = await db.get("friends", {user}, {id: 1});
friendPosts = [];
for (let id in friendIds) {
friendPosts = friendPosts.concat( await db.get("posts", {user: id}) );
}
// etc.
}
Dit zal elke zoekopdracht doen om de berichten van elke vriend in serie te krijgen, maar ze kunnen tegelijkertijd worden gedaan:
// In parallel
async function getFriendPosts(user) {
friendIds = await.db.get("friends", {user}, {id: 1});
friendPosts = await Promise.all( friendIds.map(id =>
db.get("posts", {user: id})
);
// etc.
}
Hiermee wordt de lijst met ID's herhaald om een reeks beloften te maken. await
wacht tot alle beloften zijn voltooid. Promise.all
combineert ze in één enkele belofte, maar ze worden parallel uitgevoerd.