Loosst eis Async / Await am JavaScript kucken mat Beispiller

Den Auteur vum Artikel iwwerpréift Beispiller vun Async / Await am JavaScript. Am Allgemengen ass Async / Await e praktesche Wee fir asynchrone Code ze schreiwen. Ier dës Fonktioun erschéngt, gouf sou Code geschriwwe mat Callbacks a Verspriechen. Den Auteur vum ursprénglechen Artikel weist d'Virdeeler vun Async / Await andeems Dir verschidde Beispiller analyséiert.

Mir erënneren Iech: fir all Habr Lieser - eng Remise vun 10 Rubel wann Dir Iech an all Skillbox Cours aschreift mat dem Habr Promo Code.

Skillbox recommandéiert: Online pädagogesch Cours "Java Entwéckler".

callback

Callback ass eng Funktioun deem säin Uruff onbestëmmt verspéit ass. Virdrun goufen Callbacks an deene Codeberäicher benotzt, wou d'Resultat net direkt kritt ka ginn.

Hei ass e Beispill fir asynchron e Fichier am Node.js ze liesen:

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

Probleemer entstinn wann Dir e puer asynchron Operatiounen gläichzäiteg maache musst. Loosst eis dëst Szenario virstellen: eng Ufro gëtt un d'Arfat Benotzerdatenbank gemaach, Dir musst säi Profil_img_url Feld liesen an e Bild vum Someserver.com Server eroflueden.
Nom Download konvertéiere mir d'Bild an en anert Format, zum Beispill vu PNG op JPEG. Wann d'Konversioun erfollegräich war, gëtt e Bréif un d'E-Mail vum Benotzer geschéckt. Als nächst gëtt d'Informatioun iwwer d'Evenement an d'transformations.log Datei aginn, déi den Datum uginn.

Et ass derwäert oppassen op d'Iwwerlappung vun Callbacks an déi grouss Zuel vun }) am leschten Deel vum Code. Et gëtt Callback Hell oder Pyramid of Doom genannt.

D'Nodeeler vun dëser Method sinn kloer:

  • Dëse Code ass schwéier ze liesen.
  • Et ass och schwéier Feeler ze handhaben, wat dacks zu enger schlechter Codequalitéit féiert.

Fir dëse Problem ze léisen, goufen Verspriechen op JavaScript bäigefüügt. Si erlaben Iech déif Nesting vun Callbacks ze ersetzen mam Wuert .dann.

De positiven Aspekt vu Verspriechen ass datt se de Code vill besser liesbar maachen, vun uewe bis ënnen anstatt vu lénks op riets. Wéi och ëmmer, Verspriechen hunn och hir Probleemer:

  • Dir musst der vill .dann.
  • Amplaz Try/catch, gëtt .catch benotzt fir all Feeler ze handhaben.
  • Mat multiple Verspriechen an enger Loop ze schaffen ass net ëmmer praktesch; an e puer Fäll komplizéiere se de Code.

Hei ass e Problem deen d'Bedeitung vum leschte Punkt weist.

Ugeholl, mir hunn eng For-Loop déi eng Sequenz vun Zuelen vun 0 bis 10 an zoufälleg Intervalle (0-n Sekonnen) dréckt. Mat Verspriechen, musst Dir dës Loop änneren, sou datt d'Zuelen an der Sequenz vun 0 op 10 gedréckt ginn. Also, wann et 6 Sekonnen dauert fir eng Null ze drécken an 2 Sekonnen fir eng ze drécken, soll d'Null als éischt gedréckt ginn, an dann de Countdown fir d'Dréckerei fänkt un.

An natierlech benotze mir net Async / Await oder .sort fir dëse Problem ze léisen. Eng Beispill Léisung ass um Enn.

Async Funktiounen

D'Zousatz vun Async Funktiounen am ES2017 (ES8) vereinfacht d'Aufgab fir mat Verspriechen ze schaffen. Ech bemierken datt d'Async Funktiounen "op Top" vu Verspriechen funktionnéieren. Dës Funktiounen representéieren net qualitativ verschidde Konzepter. Async Funktiounen sinn als Alternativ zum Code geduecht deen Verspriechen benotzt.

Async / Await mécht et méiglech Aarbecht mat asynchronous Code an engem synchrone Stil ze organiséieren.

Also, Verspriechen ze kennen mécht et méi einfach d'Prinzipien vun Async / Await ze verstoen.

Siwebiergen

Normalerweis besteet et aus zwee Schlësselwieder: async an waarden. Dat éischt Wuert mécht d'Funktioun an asynchron. Esou Funktiounen erlaben de Gebrauch vun waarden. An all anere Fall wäert d'Benotzung vun dëser Funktioun e Feeler generéieren.

// With function declaration
 
async function myFn() {
  // await ...
}
 
// With arrow function
 
const myFn = async () => {
  // await ...
}
 
function myFn() {
  // await fn(); (Syntax Error since no async)
}
 

Async gëtt um Ufank vun der Funktiounserklärung agebaut, an am Fall vun enger Pfeilfunktioun, tëscht dem "=" Zeechen an de Klammern.

Dës Funktiounen kënnen an engem Objet als Methode plazéiert ginn oder an enger Klasserklärung benotzt ginn.

// 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! Et ass derwäert ze erënneren datt Klassekonstruktoren a Getter / Setter net asynchron sinn.

Semantik an Ausféierungsregelen

Async Funktiounen sinn am Fong ähnlech wéi Standard JS Funktiounen, awer et ginn Ausnahmen.

Also, Async Funktiounen ginn ëmmer Verspriechen zréck:

async function fn() {
  return 'hello';
}
fn().then(console.log)
// hello

Speziell, fn gëtt de String Hallo zréck. Gutt, well dëst eng asynchron Funktioun ass, gëtt de Stringwäert an engem Versprieche mat engem Konstruktor gewéckelt.

Hei ass en alternativen Design ouni Async:

function fn() {
  return Promise.resolve('hello');
}
 
fn().then(console.log);
// hello

An dësem Fall gëtt d'Versprieche "manuell" zréckginn. Eng asynchron Funktioun ass ëmmer an engem neie Versprieche gewéckelt.

Wann de Retourwäert e primitiv ass, gëtt d'Async Funktioun de Wäert zréck andeems se en an engem Versprieche wéckelt. Wann de Retourwäert e Verspriechen Objet ass, gëtt seng Opléisung an engem neie Verspriechen zréckginn.

const p = Promise.resolve('hello')
p instanceof Promise;
// true
 
Promise.resolve(p) === p;
// true
 

Awer wat geschitt wann et e Feeler an enger asynchroner Funktioun ass?

async function foo() {
  throw Error('bar');
}
 
foo().catch(console.log);

Wann et net veraarbecht gëtt, wäert foo () e Versprieche mat Oflehnung zréckginn. An dëser Situatioun gëtt Promise.reject mat engem Feeler zréckginn amplaz Promise.resolve.

Async Funktiounen produzéieren ëmmer e Verspriechen, onofhängeg vun deem wat zréckgeet.

Asynchrone Funktiounen Paus op all Erwaardung.

Waart beaflosst Ausdréck. Also, wann den Ausdrock e Verspriechen ass, gëtt d'Async Funktioun suspendéiert bis d'Versprieche erfëllt ass. Wann den Ausdrock kee Verspriechen ass, gëtt et iwwer Promise.resolve an e Verspriechen ëmgewandelt an dann ofgeschloss.

// 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);

An hei ass eng Beschreiwung wéi d'fn Funktioun funktionnéiert.

  • Nodeems Dir et opgeruff huet, gëtt déi éischt Zeil vun const a = wait 9 ëmgewandelt; an const a = waarden Promise.resolve (9);.
  • Nodeems Dir Await benotzt, gëtt d'Funktiounsausféierung suspendéiert bis a säi Wäert kritt (an der aktueller Situatioun ass et 9).
  • delayAndGetRandom (1000) pauséiert d'Ausféierung vun der fn Funktioun bis se sech selwer fäerdeg mécht (no 1 Sekonn). Dëst stoppt effektiv d'fn Funktioun fir 1 Sekonn.
  • delayAndGetRandom (1000) via Resolve gëtt en zoufällege Wäert zréck, deen dann un d'Variabel b zougewisen gëtt.
  • Gutt, de Fall mat Variabel c ass ähnlech wéi de Fall mat Variabel a. Duerno stoppt alles fir eng Sekonn, awer elo gëtt delayAndGetRandom(1000) näischt zréck, well et net erfuerderlech ass.
  • Als Resultat ginn d'Wäerter mat der Formel a + b * c berechent. D'Resultat ass an engem Versprieche gewéckelt mat Promise.resolve a vun der Funktioun zréckginn.

Dës Pausen erënnere vläicht un Generatoren an ES6, awer et ass eppes Är Grënn.

De Problem léisen

Gutt, loosst eis elo d'Léisung vum uewe genannte Problem kucken.

D'finishMyTask Funktioun benotzt Await fir op d'Resultater vun Operatiounen ze waarden wéi queryDatabase, sendEmail, logTaskInFile, an anerer. Wann Dir dës Léisung mat där vergläicht wou Verspriechen benotzt goufen, ginn d'Ähnlechkeeten offensichtlech. Wéi och ëmmer, d'Async / Await Versioun vereinfacht immens all syntaktesch Komplexitéiten. An dësem Fall gëtt et keng grouss Zuel vu Callbacks a Ketten wéi .then/.catch.

Hei ass eng Léisung mat der Ausgab vun Zuelen, et ginn zwou Méiglechkeeten.

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);
    });
  });
};

An hei ass eng Léisung mat Async Funktiounen.

async function printNumbersUsingAsync() {
  for (let i = 0; i < 10; i++) {
    await wait(i, Math.random() * 1000);
    console.log(i);
  }
}

Feeler Veraarbechtung

Onbehandelt Feeler sinn an engem verworfene Versprieche gewéckelt. Wéi och ëmmer, Asynchroniséierungsfunktiounen kënnen probéieren / fänken fir Fehler synchron ze handhaben.

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 () ass eng asynchron Funktioun, déi entweder geléngt ("perfekt Zuel") oder mat engem Feeler feelt ("Sorry, Zuel ze grouss").

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

Zënter dem Beispill hei uewen erwaart canRejectOrReturn auszeféieren, wäert säin eegene Versoen zu der Ausféierung vum Fangblock resultéieren. Als Resultat wäert d'Funktioun foo entweder ondefinéiert ophalen (wann näischt am Versuchblock zréckgeet) oder mat engem Feeler gefaangen. Als Resultat wäert dës Funktioun net falen, well de Versuch / Fang d'Funktioun foo selwer handhabt.

Hei ass en anert Beispill:

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

Et ass derwäert opmierksam op d'Tatsaach ze bezuelen datt am Beispill canRejectOrReturn vu foo zréckkomm ass. Foo an dësem Fall schléisst entweder mat enger perfekter Zuel op oder gëtt e Feeler zréck ("Sorry, Zuel ze grouss"). De Fangblock gëtt ni ausgefouert.

De Problem ass datt foo d'Versprieche vum canRejectOrReturn zréckginn. Also d'Léisung fir foo gëtt d'Léisung fir CanRejectOrReturn. An dësem Fall besteet de Code aus nëmmen zwou Zeilen:

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

Hei ass wat geschitt wann Dir await a retour zesummen benotzt:

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

Am Code uewen, foo wäert erfollegräich Sortie mat souwuel eng perfekt Zuel an engem Feeler gefaangen. Et gëtt keng Refusen hei. Mee foo wäert zréck mat canRejectOrReturn, net mat ondefinéiert. Loosst eis dëst sécherstellen andeems Dir d'Retour await canRejectOrReturn () Linn erofhuelen:

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

Gemeinsam Feeler a Fallen

A verschiddene Fäll kann d'Benotzung vun Async / Await zu Feeler féieren.

Vergiess waarden

Dëst geschitt zimlech dacks - d'Waarden Schlësselwuert ass vergiess virum Verspriechen:

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

Wéi Dir gesitt, gëtt et kee Waarden oder Retour am Code. Dofir geet foo ëmmer mat ondefinéiert ouni 1 Sekonn Retard. Awer d'Versprieche wäert erfëllt ginn. Wann et e Feeler oder Oflehnung werft, da gëtt UnhandledPromiseRejectionWarning genannt.

Async Funktiounen an Callbacks

Async Funktiounen ginn zimlech oft an .map oder .filter als Callbacks benotzt. E Beispill ass d'FetchPublicReposCount(Benotzernumm) Funktioun, déi d'Zuel vun oppene Repositories op GitHub zréckginn. Loosst eis soen datt et dräi Benotzer sinn deenen hir Metriken mir brauchen. Hei ass de Code fir dës Aufgab:

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'];
}

Mir brauchen ArfatSalman, octocat, norvig Konte. An dësem Fall maache mir:

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

Et ass derwäert oppassen op Await am .map Callback. Hei zielt ass eng Rei vu Verspriechen, an .map ass en anonyme Réckruff fir all spezifizéierte Benotzer.

Zevill konsequent Notzung vun waarden

Loosst eis dëse Code als Beispill huelen:

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;
}

Hei gëtt d'Repo-Nummer an d'Zählvariabel gesat, da gëtt dës Zuel un d'Zählungsarray bäigefüügt. De Problem mam Code ass datt bis d'Donnéeën vum éischte Benotzer vum Server ukommen, all spéider Benotzer am Standby-Modus sinn. Sou gëtt nëmmen ee Benotzer gläichzäiteg veraarbecht.

Wann et zum Beispill ongeféier 300 ms dauert fir ee Benotzer ze veraarbecht, dann ass et fir all Benotzer schonn eng Sekonn; d'Zäit déi linear verbraucht hänkt vun der Unzuel vun de Benotzer of. Awer well d'Zuel vun de Repo erhalen hänkt net vuneneen of, kënnen d'Prozesser paralleliséiert ginn. Dëst erfuerdert mat .map a Promise.all ze schaffen:

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

Promise.all kritt eng Rei vu Verspriechen als Input a gëtt e Verspriechen zréck. Déi lescht ass ofgeschloss nodeems all Verspriechen am Array ofgeschloss sinn oder bei der éischter Oflehnung. Et ka geschéien datt se net all gläichzäiteg ufänken - fir gläichzäiteg Start ze garantéieren, kënnt Dir p-map benotzen.

Konklusioun

Async Funktiounen ginn ëmmer méi wichteg fir d'Entwécklung. Gutt, fir adaptiv Notzung vun Async Funktiounen ass et derwäert ze benotzen Async Iteratoren. E JavaScript Entwéckler sollt gutt an dësem beherrscht sinn.

Skillbox recommandéiert:

Source: will.com

Setzt e Commentaire