Fighjemu Async / Await in JavaScript usendu esempi
L'autore di l'articulu esamina esempi di Async / Await in JavaScript. In generale, Async / Await hè un modu convenientu per scrive codice asincronu. Prima di sta funzione apparsu, tali codice hè statu scrittu cù callbacks è prumessi. L'autore di l'articulu originale revela i vantaghji di Async/Await analizendu diversi esempi.
Ramintemu:per tutti i lettori di "Habr" - un scontu di 10 000 rubles quandu si iscrizzione in ogni cursu Skillbox cù u codice promozionale "Habr".
Callback hè una funzione chì a chjama hè ritardata indefinitu. Prima, i callbacks sò stati utilizati in quelli spazii di codice induve u risultatu ùn pudia esse ottenutu immediatamente.
Eccu un esempiu di lettura asincrona di un schedariu in Node.js:
I prublemi sò quandu avete bisognu di fà parechje operazioni asincrone à una volta. Imaginemu stu scenariu: una dumanda hè fatta à a basa di dati d'utilizatori Arfat, avete bisognu di leghje u so campu profile_img_url è scaricate una maghjina da u servitore someserver.com.
Dopu avè scaricatu, cunvertemu l'imaghjini in un altru formatu, per esempiu da PNG à JPEG. Se a cunversione hè successu, una lettera hè mandata à l'email di l'utilizatore. In seguitu, l'infurmazione nantu à l'avvenimentu hè inserita in u schedariu transformations.log, indicà a data.
Hè vale a pena attente à a superposizione di callbacks è u gran numaru di }) in a parte finale di u codice. Hè chjamatu Callback Hell o Pyramid of Doom.
I svantaghji di stu metudu sò evidenti:
Stu codice hè difficiule à leghje.
Hè ancu difficiuli di trattà l'errori, chì spessu porta à una qualità di codice povira.
Per risolve stu prublema, i prumessi sò stati aghjuntu à JavaScript. Permettenu di rimpiazzà a nidificazione prufonda di callbacks cù a parolla .then.
L'aspettu pusitivu di e prumesse hè chì facenu u codice assai megliu leggibile, da cima à fondu piuttostu cà da manca à diritta. Tuttavia, i prumessi anu ancu i so prublemi:
Avete bisognu di aghjunghje assai .then.
Invece di pruvà / catch, .catch hè utilizatu per trattà tutti i sbagli.
U travagliu cù parechje prumesse in un ciclu ùn hè micca sempre cunvene in certi casi, complicà u codice.
Eccu un prublema chì mostrarà u significatu di l'ultimu puntu.
Supposons qu'on ait un cycle for qui imprime une séquence de nombres de 0 à 10 à intervalles aléatoires (0-n secondes). Utilizà e prumesse, avete bisognu di cambià stu loop per chì i numeri sò stampati in sequenza da 0 à 10. Allora, se ci vole 6 seconde per stampà un cero è 2 seconde per stampà unu, u cero deve esse stampatu prima, è dopu. u countdown per stampà quellu chì principia.
È sicuru, ùn avemu micca aduprà Async / Await o .sort per risolve stu prublema. Un esempiu di suluzione hè à a fine.
Funzioni Async
L'aghjunzione di funzioni async in ES2017 (ES8) simplificà u travagliu di travaglià cù promesse. Aghju nutatu chì e funzioni async funzionanu "in cima" di e prumesse. Queste funzioni ùn rapprisentanu cuncetti qualitativamente differenti. E funzioni Async sò pensate cum'è una alternativa à u codice chì usa promesse.
Async/Await permette di urganizà u travagliu cù codice asincronu in un stile sincronu.
Cusì, a cunniscenza di e prumesse rende più faciule per capiscenu i principii di Async / Await.
fuori
Normalmente si compone di duie parole chjave: async è await. A prima parolla trasforma a funzione in asincrona. Tali funzioni permettenu l'usu di aspittà. In ogni altru casu, utilizendu sta funzione generà un errore.
// With function declaration
async function myFn() {
// await ...
}
// With arrow function
const myFn = async () => {
// await ...
}
function myFn() {
// await fn(); (Syntax Error since no async)
}
Async hè inseritu à u principiu di a dichjarazione di a funzione, è in u casu di una funzione di freccia, trà u signu "=" è i parentesi.
Queste funzioni ponu esse piazzate in un oggettu cum'è metudi o aduprate in una dichjarazione di classi.
// As an object's method
const obj = {
async getName() {
return fetch('https://www.example.com');
}
}
// In a class
class Obj {
async getResource() {
return fetch('https://www.example.com');
}
}
NB! Hè vale a pena ricurdà chì i custruttori di classi è getters / setters ùn ponu micca esse asincroni.
Semantica è regule di esecutivu
E funzioni Async sò basamente simili à e funzioni JS standard, ma ci sò eccezzioni.
Cusì, e funzioni asincrone sempre tornanu promesse:
async function fn() {
return 'hello';
}
fn().then(console.log)
// hello
In particulare, fn torna a stringa salutu. Ebbè, postu chì questa hè una funzione asincrona, u valore di stringa hè impannillatu in una prumessa cù un constructore.
Eccu un disignu alternativu senza Async:
function fn() {
return Promise.resolve('hello');
}
fn().then(console.log);
// hello
In questu casu, a prumessa hè tornata "manualmente". Una funzione asincrona hè sempre impannillata in una nova prumessa.
Se u valore di ritornu hè un primitivu, a funzione asincrona torna u valore imballendu in una prumessa. Se u valore di ritornu hè un oggettu di prumessa, a so risoluzione hè tornata in una nova prumessa.
const p = Promise.resolve('hello')
p instanceof Promise;
// true
Promise.resolve(p) === p;
// true
Ma chì succede se ci hè un errore in una funzione asincrona?
async function foo() {
throw Error('bar');
}
foo().catch(console.log);
S'ellu ùn hè micca trattatu, foo() restituverà una prumessa cù rifiutu. In questa situazione, Promise.reject chì cuntene un errore serà tornatu invece di Promise.resolve.
E funzioni Async sempre produci una prumessa, indipendentemente da ciò chì hè tornatu.
E funzioni asincrone si mette in pausa in ogni attesa.
Await influenza l'espressioni. Allora, se l'espressione hè una prumessa, a funzione asincrona hè sospesa finu à chì a prumessa hè cumpiita. Se l'espressione ùn hè micca una prumessa, hè cunvertita in una prumessa via Promise.resolve è dopu compie.
// utility function to cause delay
// and get random value
const delayAndGetRandom = (ms) => {
return new Promise(resolve => setTimeout(
() => {
const val = Math.trunc(Math.random() * 100);
resolve(val);
}, ms
));
};
async function fn() {
const a = await 9;
const b = await delayAndGetRandom(1000);
const c = await 5;
await delayAndGetRandom(1000);
return a + b * c;
}
// Execute fn
fn().then(console.log);
È quì hè una descrizzione di cumu funziona a funzione fn.
Dopu avè chjamatu, a prima linea hè cunvertita da const a = await 9; in const a = aspetta Promise.resolve (9);.
Dopu avè usatu Await, l'esekzione di a funzione hè sospesa finu à chì a riceve u so valore (in a situazione attuale hè 9).
delayAndGetRandom(1000) mette in pausa l'esekzione di a funzione fn finu à ch'ella si compie (dopu à 1 secondu). Questu ferma in modu efficace a funzione fn per 1 secondu.
delayAndGetRandom(1000) via resolve torna un valore aleatoriu, chì hè dopu assignatu à a variàbile b.
Ebbè, u casu cù a variabile c hè simile à u casu cù a variabile a. Dopu questu, tuttu si ferma per un secondu, ma avà delayAndGetRandom (1000) ùn torna nunda perchè ùn hè micca necessariu.
In u risultatu, i valori sò calculati cù a formula a + b * c. U risultatu hè impannillatu in una prumessa cù Promise.resolve è tornatu da a funzione.
Queste pause ponu esse una reminiscenza di i generatori in ES6, ma ci hè qualcosa i vostri motivi.
Risolviri u prublema
Ebbè, avà andemu à vede a suluzione à u prublema mintuatu sopra.
A funzione finishMyTask usa Await per aspittà i risultati di l'operazioni cum'è queryDatabase, sendEmail, logTaskInFile, è altri. Se paragunate sta suluzione cù quella induve e prumesse sò state aduprate, e similitudini diventeranu evidenti. Tuttavia, a versione Async / Await simplifica assai tutte e cumplessità sintattiche. In questu casu, ùn ci hè micca un gran numaru di callbacks è catene cum'è .then/.catch.
Eccu una suluzione cù a pruduzzioni di numeri, ci sò duie opzioni.
const wait = (i, ms) => new Promise(resolve => setTimeout(() => resolve(i), ms));
// Implementation One (Using for-loop)
const printNumbers = () => new Promise((resolve) => {
let pr = Promise.resolve(0);
for (let i = 1; i <= 10; i += 1) {
pr = pr.then((val) => {
console.log(val);
return wait(i, Math.random() * 1000);
});
}
resolve(pr);
});
// Implementation Two (Using Recursion)
const printNumbersRecursive = () => {
return Promise.resolve(0).then(function processNextPromise(i) {
if (i === 10) {
return undefined;
}
return wait(i, Math.random() * 1000).then((val) => {
console.log(val);
return processNextPromise(i + 1);
});
});
};
È quì hè una suluzione cù e funzioni async.
async function printNumbersUsingAsync() {
for (let i = 0; i < 10; i++) {
await wait(i, Math.random() * 1000);
console.log(i);
}
}
Trattamentu di errore
L'errori micca gestiti sò impannillati in una prumessa rifiutata. Tuttavia, e funzioni async ponu aduprà try / catch per trattà l'errori in modu sincronu.
async function canRejectOrReturn() {
// wait one second
await new Promise(res => setTimeout(res, 1000));
// Reject with ~50% probability
if (Math.random() > 0.5) {
throw new Error('Sorry, number too big.')
}
return 'perfect number';
}
canRejectOrReturn() hè una funzione asincrona chì o riesce ("numeru perfettu") o falla cù un errore ("Scusate, numeru troppu grande").
Siccomu l'esempiu sopra aspetta chì canRejectOrReturn eseguisce, u so propiu fallimentu risulterà in l'esekzione di u bloccu catch. In u risultatu, a funzione foo finisce cù un indefinitu (quandu nunda hè tornatu in u bloccu di prova) o cù un errore catturatu. In u risultatu, sta funzione ùn falla micca perchè u try / catch hà da trattà a funzione foo stessu.
Hè vale a pena attente à u fattu chì in l'esempiu, canRejectOrReturn hè tornatu da foo. Foo in questu casu finisce cù un numeru perfettu o torna un Errore ("Scusate, numeru troppu grande"). U bloccu catch ùn serà mai eseguitu.
U prublema hè chì foo torna a prumessa passata da canRejectOrReturn. Allora a suluzione à foo diventa a suluzione per canRejectOrReturn. In questu casu, u codice serà custituitu da solu duie linee:
In u codice sopra, foo esce cun successu cù un numeru perfettu è un errore catturatu. Ùn ci sarà micca rifiutu quì. Ma foo tornerà cù canRejectOrReturn, micca cun undefined. Assicuremu di questu eliminendu a linea di ritornu await canRejectOrReturn() :
Comu pudete vede, ùn ci hè micca aspittà o ritornu in u codice. Dunque foo esce sempre cun undefined senza un ritardu di 1 seconda. Ma a prumessa serà cumpiita. Se lancia un errore o un rifiutu, allora UnhandledPromiseRejectionWarning serà chjamatu.
Funzioni Async in Callbacks
E funzioni Async sò abbastanza spessu usate in .map o .filter cum'è callbacks. Un esempiu hè a funzione fetchPublicReposCount (username), chì torna u numeru di repositori aperti in GitHub. Diciamu chì ci sò trè utilizatori chì e metriche avemu bisognu. Eccu u codice per questu compitu:
Hè vale a pena attente à Await in u callback .map. Quì cunta hè una serie di prumesse, è .map hè un callback anonimu per ogni utilizatore specificatu.
Usu eccessivamente consistente di aspittà
Pigliemu stu codice cum'è un esempiu:
async function fetchAllCounts(users) {
const counts = [];
for (let i = 0; i < users.length; i++) {
const username = users[i];
const count = await fetchPublicReposCount(username);
counts.push(count);
}
return counts;
}
Quì u numeru repo hè piazzatu in a variàbile di u cuntu, dopu stu numeru hè aghjuntu à l'array di cunti. U prublema cù u codice hè chì finu à chì i dati di u primu utilizatore ghjunghjenu da u servitore, tutti l'utilizatori sussegwenti seranu in modu standby. Cusì, solu un utilizatore hè trattatu à tempu.
Se, per esempiu, ci vole circa 300 ms per processà un utilizatore, allora per tutti l'utilizatori hè digià un secondu u tempu passatu linearmente dipende da u numeru di utilizatori. Ma postu chì ottene u numeru di repo ùn dipende micca di l'altri, i prucessi ponu esse paralleli. Questu hè bisognu di travaglià cù .map è Promise.all:
Promise.all riceve una serie di promesse cum'è input è torna una prumessa. L'ultime hè cumpletu dopu chì tutte e promesse in a matrice sò cumplette o à u primu rejetu. Pò accade chì tutti ùn cumincianu micca à u stessu tempu - per assicurà a partenza simultanea, pudete aduprà p-map.
cunchiusioni
E funzioni Async sò diventate sempre più impurtanti per u sviluppu. Ebbè, per l'usu adattativu di e funzioni async, duvete aduprà Iteratori Async. Un sviluppatore JavaScript deve esse bè versatu in questu.