Sök…
Introduktion
async
och await
bygga på toppen av löften och generatorer för att uttrycka asynkrona handlingar inline. Detta gör asynkron eller återuppringningskod mycket lättare att underhålla.
Funktioner med sökordet async
returnerar ett Promise
och kan kallas med den syntaxen.
Inuti en async function
kan nyckelordet await
tillämpas på valfritt Promise
och kommer att leda till att hela funktionsorganet efter att await
körs efter att löften löses.
Syntax
- async-funktion foo () {
...
vänta på asyncCall ()
} - async-funktion () {...}
- async () => {...}
- (async () => {
const data = vänta på asyncCall ()
console.log (data)}) ()
Anmärkningar
Async-funktioner är ett syntaktiskt socker framför löften och generatorer. De hjälper dig att göra din kod mer läsbar, underhållbar, lättare att fånga fel i och med färre indragningsnivåer.
Introduktion
En funktion som definieras som async
är en funktion som kan utföra asynkrona åtgärder men fortfarande ser synkron ut. Hur det görs är att använda nyckelordet för att await
att skjuta upp funktionen medan den väntar på att ett löfte löser eller avvisar.
Obs: Async-funktioner är ett steg 4 ("Färdigt") förslag på spår som ska inkluderas i ECMAScript 2017-standarden.
Använd t.ex. det löftebaserade 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);
}
}
En async-funktion returnerar alltid ett löfte själv, så du kan använda den i andra asynkrona funktioner.
Pilfunktionsstil
const getJSON = async url => {
const response = await fetch(url);
return await response.json();
}
Mindre intryck
Med löften:
function doTheThing() {
return doOneThing()
.then(doAnother)
.then(doSomeMore)
.catch(handleErrors)
}
Med async-funktioner:
async function doTheThing() {
try {
const one = await doOneThing();
const another = await doAnother(one);
return await doSomeMore(another);
} catch (err) {
handleErrors(err);
}
}
Lägg märke till hur returen är längst ner och inte längst upp, och du använder språkets ursprungliga felhanteringsmekanik ( try/catch
).
Vänta och operatörens företräde
Du måste ha operatörens prioritet i åtanke när du använder nyckelordet await
.
Föreställ dig att vi har en asynkron funktion som kallar en annan asynkron funktion, getUnicorn()
som returnerar ett löfte som löser till en instans av klass Unicorn
. Nu vill vi få storleken på enhörningen med getSize()
-metoden för den klassen.
Titta på följande kod:
async function myAsyncFunction() {
await getUnicorn().getSize();
}
Vid första anblicken verkar det giltigt, men det är det inte. På grund av operatörens förekomst motsvarar det följande:
async function myAsyncFunction() {
await (getUnicorn().getSize());
}
Här försöker vi kalla getSize()
för Promise-objektet, vilket inte är vad vi vill ha.
Istället bör vi använda parenteser för att ange att vi först vill vänta på enhörningen och sedan ringa getSize()
-metoden för resultatet:
async function asyncFunction() {
(await getUnicorn()).getSize();
}
Självklart. den föregående versionen kan vara giltig i vissa fall, till exempel om getUnicorn()
-funktionen var synkron, men getSize()
-metoden var asynkron.
Async-funktioner jämfört med löften
async
funktioner ersätter inte Promise
typen; de lägger till språkord som gör löften enklare att ringa. De är utbytbara:
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) { ... }
Alla funktioner som använder löftskedjor kan skrivas om med 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));
}
Funktionen kan skrivas om med hjälp av async
/ await
enligt följande:
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);
}
}
Denna async
variant av newUnicorn()
verkar ge ett Promise
, men det fanns verkligen flera await
nyckelord. Var och en gav tillbaka ett Promise
, så vi hade verkligen en samling löften snarare än en kedja.
I själva verket kan vi tänka på det som en function*
, var och en await
vara ett yield new Promise
. Men resultaten av varje löfte behövs av nästa för att fortsätta funktionen. Därför behövs det ytterligare nyckelordet async
för funktionen (liksom det await
nyckelordet när man ringer löften) eftersom det berättar Javascript att automatiskt skapar en observatör för den här iterationen. Promise
returneras av async function newUnicorn()
löses när denna iteration är klar.
I praktiken behöver du inte ta hänsyn till det; await
döljer löfte och async
döljer generatorgenereringen.
Du kan ringa async
funktioner som om de var löften och await
alla löften eller någon async
funktion. Du behöver inte await
en async-funktion, precis som du kan utföra ett löfte utan .then()
.
Du kan också använda en async
IIFE om du vill köra den koden omedelbart:
(async () => {
await makeCoffee()
console.log('coffee is ready!')
})()
Looping med async väntar
När du använder async väntar i öglor kan du stöta på några av dessa problem.
Om du bara försöker använda vänta inuti för forEach
kommer det att kasta ett Unexpected token
.
(async() => {
data = [1, 2, 3, 4, 5];
data.forEach(e => {
const i = await somePromiseFn(e);
console.log(i);
});
})();
Detta kommer av det faktum att du felaktigt har sett pilens funktion som ett block. await
kommer att ske i samband med återuppringningsfunktionen, som inte är async
.
Tolkaren skyddar oss från att göra ovanstående fel, men om du lägger till async
i forEach
återuppringningen forEach
inga fel. Du kanske tror att detta löser problemet, men det fungerar inte som förväntat.
Exempel:
(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');
})();
Detta händer eftersom funktionen för återuppringning av asynk bara kan pausa sig själv, inte förälderens asynkfunktion.
Du kan skriva en asyncForEach-funktion som returnerar ett löfte och sedan kan du något liknande
await asyncForEach(async (e) => await somePromiseFn(e), data )
I grund och botten returnerar du ett löfte som löses när alla återuppringningar väntar och görs. Men det finns bättre sätt att göra detta, och det är bara att använda en slinga.
Du kan använda en for-of
loop eller en for/while
slinga, det spelar ingen roll vilken du väljer.
(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');
})();
Men det finns en annan fångst. Denna lösning väntar på att varje samtal till somePromiseFn
ska slutföras innan den itereras över nästa.
Det här är bra om du faktiskt vill att dina somePromiseFn
kallelser ska utföras i ordning, men om du vill att de ska köras samtidigt måste du await
på 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
får en rad löften som sin enda parameter och ger ett löfte. När alla löften i arrayen löses, löses också det återlämnade löfte. Vi await
på detta löfte och när det löses är alla våra värden tillgängliga.
Ovanstående exempel är helt körbara. Funktionen somePromiseFn
kan göras som en async-ekofunktion med en timeout. Du kan prova exemplen i babel-repl med åtminstone stage-3
förinställningen och titta på utgången.
function somePromiseFn(n) {
return new Promise((res, rej) => {
setTimeout(() => res(n), 250);
});
}
Samtidig asynk (parallell) operation
Ofta vill du utföra asynkrona operationer parallellt. Det finns direkt syntax som stöder detta i async
/ await
förslag, men eftersom await
kommer att vänta på ett löfte, kan du linda flera löften tillsammans i Promise.all
vänta för dem:
// 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.
}
Detta gör varje fråga för att få varje väns inlägg seriellt, men de kan göras samtidigt:
// 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.
}
Detta kommer att slinga över listan över ID: er för att skapa en rad löften. await
väntar på att alla löften är fullständiga. Promise.all
kombinerar dem till ett enda löfte, men de görs parallellt.