Asynchronní/čekající

Existuje speciální syntaxe pro pohodlnější práci se sliby, která se nazývá „asynchronní/čekající“. Je to překvapivě snadné na pochopení a použití.

Asynchronní funkce

Začněme s async klíčové slovo. Může být umístěn před funkcí takto:

async function f() {
 return 1;
}

Slovo „asynchronní“ před funkcí znamená jednu jednoduchou věc:funkce vždy vrátí slib. Ostatní hodnoty jsou automaticky zabaleny do vyřešeného příslibu.

Tato funkce například vrátí vyřešený příslib s výsledkem 1; pojďme to otestovat:

async function f() {
 return 1;
}

f().then(alert); // 1

…Mohli bychom výslovně vrátit slib, což by bylo stejné:

async function f() {
 return Promise.resolve(1);
}

f().then(alert); // 1

Takže async zajišťuje, že funkce vrátí slib a zabalí do něj nesliby. Dost jednoduché, že? Ale nejen to. Existuje další klíčové slovo, await , který funguje pouze uvnitř async funkcí a je to docela fajn.

Počkejte

Syntaxe:

// works only inside async functions
let value = await promise;

Klíčové slovo await nutí JavaScript čekat, dokud se tento slib nevyrovná a vrátí výsledek.

Zde je příklad se slibem, který se vyřeší za 1 sekundu:

async function f() {

 let promise = new Promise((resolve, reject) => {
 setTimeout(() => resolve("done!"), 1000)
 });

 let result = await promise; // wait until the promise resolves (*)

 alert(result); // "done!"
}

f();

Provádění funkce se „pozastaví“ na řádku (*) a pokračuje, když se příslib vyrovná, s result stává jejím výsledkem. Výše uvedený kód tedy ukazuje „hotovo!“ za jednu sekundu.

Zdůrazněme:await doslova pozastaví provádění funkce, dokud se příslib nevyrovná, a poté jej obnoví s výsledkem příslibu. To nestojí žádné prostředky CPU, protože JavaScript engine může mezitím vykonávat jiné úlohy:spouštět jiné skripty, zpracovávat události atd.

Je to jen elegantnější syntaxe pro dosažení slibného výsledku než promise.then . A je snazší číst a psát.

await nelze použít v běžných funkcích

Pokud se pokusíme použít await v neasynchronní funkci by došlo k chybě syntaxe:

function f() {
 let promise = Promise.resolve(1);
 let result = await promise; // Syntax error
}

Tato chyba se může zobrazit, pokud zapomeneme zadat async před funkcí. Jak bylo uvedeno dříve, await funguje pouze uvnitř async funkce.

Vezměme showAvatar() příklad z kapitoly Řetězení slibů a přepište jej pomocí async/await :

  1. Budeme muset nahradit .then volání s await .
  2. Také bychom měli vytvořit funkci async aby mohli pracovat.
async function showAvatar() {

 // read our JSON
 let response = await fetch('/article/promise-chaining/user.json');
 let user = await response.json();

 // read github user
 let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
 let githubUser = await githubResponse.json();

 // show the avatar
 let img = document.createElement('img');
 img.src = githubUser.avatar_url;
 img.className = "promise-avatar-example";
 document.body.append(img);

 // wait 3 seconds
 await new Promise((resolve, reject) => setTimeout(resolve, 3000));

 img.remove();

 return githubUser;
}

showAvatar();

Docela čisté a snadno čitelné, že? Mnohem lepší než předtím.

Moderní prohlížeče umožňují nejvyšší úroveň await v modulech

V moderních prohlížečích await na nejvyšší úrovni funguje dobře, když jsme uvnitř modulu. Modulům se budeme věnovat v článku Moduly, úvod.

Například:

// we assume this code runs at top level, inside a module
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

console.log(user);

Pokud nepoužíváme moduly nebo musí být podporovány starší prohlížeče, existuje univerzální recept:zabalení do anonymní asynchronní funkce.

Takhle:

(async () => {
 let response = await fetch('/article/promise-chaining/user.json');
 let user = await response.json();
 ...
})();
await přijímá „thenables“

Jako promise.then , await nám umožňuje používat potomovatelné objekty (ty s volatelným then metoda). Myšlenka je taková, že objekt třetí strany nemusí být příslibem, ale může být kompatibilní:pokud podporuje .then , to stačí k použití s ​​await .

Zde je ukázka Thenable třída; await níže přijímá jeho instance:

class Thenable {
 constructor(num) {
 this.num = num;
 }
 then(resolve, reject) {
 alert(resolve);
 // resolve with this.num*2 after 1000ms
 setTimeout(() => resolve(this.num * 2), 1000); // (*)
 }
}

async function f() {
 // waits for 1 second, then result becomes 2
 let result = await new Thenable(1);
 alert(result);
}

f();

Pokud await získá neslíbený objekt s .then , volá tuto metodu poskytující vestavěné funkce resolve a reject jako argumenty (stejně jako u běžného Promise vykonavatel). Potom await čeká, až se zavolá jeden z nich (ve výše uvedeném příkladu se to stane na řádku (*) ) a poté pokračuje s výsledkem.

Metody asynchronních tříd

Chcete-li deklarovat metodu asynchronní třídy, stačí před ni přidat async :

class Waiter {
 async wait() {
 return await Promise.resolve(1);
 }
}

new Waiter()
 .wait()
 .then(alert); // 1 (this is the same as (result => alert(result)))

Význam je stejný:zajišťuje, že vrácená hodnota je příslib a umožňuje await .

Ošetření chyb

Pokud se příslib vyřeší normálně, pak await promise vrátí výsledek. Ale v případě odmítnutí vyhodí chybu, jako kdyby tam bylo throw prohlášení na tomto řádku.

Tento kód:

async function f() {
 await Promise.reject(new Error("Whoops!"));
}

…je stejné jako toto:

async function f() {
 throw new Error("Whoops!");
}

V reálných situacích může slib nějakou dobu trvat, než bude odmítnut. V takovém případě dojde ke zpoždění před await vyvolá chybu.

Tuto chybu můžeme zachytit pomocí try..catch , stejným způsobem jako běžný throw :

async function f() {

 try {
 let response = await fetch('http://no-such-url');
 } catch(err) {
 alert(err); // TypeError: failed to fetch
 }
}

f();

V případě chyby ovládací prvek skočí na catch blok. Můžeme také zalomit více řádků:

async function f() {

 try {
 let response = await fetch('/no-user-here');
 let user = await response.json();
 } catch(err) {
 // catches errors both in fetch and response.json
 alert(err);
 }
}

f();

Pokud nemáme try..catch , pak příslib generovaný voláním asynchronní funkce f() se stane odmítnutým. Můžeme připojit .catch jak to zvládnout:

async function f() {
 let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)

Pokud zapomeneme přidat .catch tam, pak dostaneme chybu neošetřeného slibu (lze zobrazit v konzole). Takové chyby můžeme zachytit pomocí globálního unhandledrejection obslužnou rutinu události, jak je popsáno v kapitole Zpracování chyb s přísliby.

async/await a promise.then/catch

Když použijeme async/await , zřídka potřebujeme .then , protože await zvládá čekání na nás. A můžeme použít běžný try..catch místo .catch . To je obvykle (ale ne vždy) pohodlnější.

Ale na nejvyšší úrovni kódu, když jsme mimo jakékoli async syntakticky nejsme schopni použít await , takže je běžnou praxí přidat .then/catch ke zpracování konečného výsledku nebo chybné chyby, jako v řádku (*) z výše uvedeného příkladu.

async/await funguje dobře s Promise.all

Když potřebujeme čekat na více příslibů, můžeme je zabalit do Promise.all a poté await :

// wait for the array of results
let results = await Promise.all([
 fetch(url1),
 fetch(url2),
 ...
]);

V případě chyby se šíří jako obvykle, z neúspěšného příslibu na Promise.all a pak se stane výjimkou, kterou můžeme zachytit pomocí try..catch kolem hovoru.

Shrnutí

async klíčové slovo před funkcí má dva účinky:

  1. Vždy vrátí slib.
  2. Povoluje await k použití v něm.

await klíčové slovo před příslibem způsobí, že JavaScript počká, dokud se příslib nevyrovná, a pak:

  1. Pokud se jedná o chybu, vygeneruje se výjimka – stejná jako v případě throw error byli povoláni právě na to místo.
  2. V opačném případě vrátí výsledek.

Společně poskytují skvělý rámec pro psaní asynchronního kódu, který se snadno čte i zapisuje.

S async/await zřídka potřebujeme psát promise.then/catch , ale stále bychom neměli zapomínat, že jsou založeny na slibech, protože někdy (např. v nejvzdálenějším rozsahu) musíme tyto metody použít. Také Promise.all je příjemné, když čekáme na mnoho úkolů současně.