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".

Skillbox consiglia: Corso educativu in linea "Sviluppatore Java".

callback

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:

fs.readFile(__filename, 'utf-8', (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
});

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").

async function foo() {
  try {
    await canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

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.

Eccu un altru esempiu:

async function foo() {
  try {
    return canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

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:

try {
    const promise = canRejectOrReturn();
    return promise;
}

Eccu ciò chì succede se utilizate aspittà è vultà inseme:

async function foo() {
  try {
    return await canRejectOrReturn();
  } catch (e) {
    return 'error caught';
  }
}

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() :

try {
    const value = await canRejectOrReturn();
    return value;
}
// …

Errori cumuni è trappule

In certi casi, utilizendu Async/Await pò purtà à errori.

Aspettu scurdatu

Questu succede abbastanza spessu - a keyword await hè scurdata prima di a prumessa:

async function foo() {
  try {
    canRejectOrReturn();
  } catch (e) {
    return 'caught';
  }
}

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:

const url = 'https://api.github.com/users';
 
// Utility fn to fetch repo counts
const fetchPublicReposCount = async (username) => {
  const response = await fetch(`${url}/${username}`);
  const json = await response.json();
  return json['public_repos'];
}

Avemu bisognu di cunti ArfatSalman, octocat, norvig. In questu casu facemu:

const users = [
  'ArfatSalman',
  'octocat',
  'norvig'
];
 
const counts = users.map(async username => {
  const count = await fetchPublicReposCount(username);
  return count;
});

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:

async function fetchAllCounts(users) {
  const promises = users.map(async username => {
    const count = await fetchPublicReposCount(username);
    return count;
  });
  return Promise.all(promises);
}

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.

Skillbox consiglia:

Source: www.habr.com

Add a comment