Ann gade nan Async/Await nan JavaScript lè l sèvi avèk egzanp

Otè atik la egzamine egzanp Async/Await nan JavaScript. An jeneral, Async/Await se yon fason pratik pou ekri kòd asynchrone. Anvan karakteristik sa a parèt, yo te ekri kòd sa yo lè l sèvi avèk callbacks ak pwomès yo. Otè atik orijinal la revele avantaj ki genyen nan Async/Await lè li analize plizyè egzanp.

Nou raple: pou tout lektè "Habr" - yon rabè nan 10 rubles lè w ap enskri nan nenpòt kou Skillbox lè l sèvi avèk kòd pwomosyon "Habr".

Skillbox rekòmande: Kou edikatif sou entènèt "Devlopè Java".

Rapèl

Callback se yon fonksyon ki gen yon apèl retade endefiniman. Précédemment, yo te itilize rapèl nan zòn sa yo nan kòd kote rezilta a pa t 'kapab jwenn imedyatman.

Isit la se yon egzanp asynchrone li yon dosye nan Node.js:

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

Pwoblèm rive lè ou bezwen fè plizyè operasyon asynchrone alafwa. Ann imajine senaryo sa a: yo fè yon demann nan baz done itilizatè Arfat, ou bezwen li jaden profile_img_url li yo epi telechaje yon imaj nan sèvè someserver.com.
Apre telechaje, nou konvèti imaj la nan yon lòt fòma, pou egzanp soti nan PNG nan JPEG. Si konvèsyon an te reyisi, yo voye yon lèt nan imel itilizatè a. Apre sa, enfòmasyon sou evènman an antre nan fichye transformations.log, ki endike dat la.

Li vo peye atansyon sou sipèpoze nan rapèl ak gwo kantite }) nan pati final la nan kòd la. Li rele lanfè Callback oswa Pyramid of Doom.

Dezavantaj yo nan metòd sa a se evidan:

  • Kòd sa a difisil pou li.
  • Li difisil tou pou okipe erè, ki souvan mennen nan bon jan kalite kòd pòv.

Pou rezoud pwoblèm sa a, pwomès yo te ajoute nan JavaScript. Yo pèmèt ou ranplase gwo twou san fon nidifikasyon nan callbacks ak mo a .then.

Aspè pozitif nan pwomès yo se yo ke yo fè kòd la pi byen lizib, soti anwo jouk anba olye ke de goch a dwat. Sepandan, pwomès yo tou gen pwoblèm yo:

  • Ou bezwen ajoute anpil nan .Lè sa a.
  • Olye pou yo eseye/trape, yo itilize .catch pou okipe tout erè.
  • Travay ak plizyè pwomès nan yon sèl bouk pa toujou pratik nan kèk ka, yo konplike kòd la.

Men yon pwoblèm ki pral montre siyifikasyon dènye pwen an.

Sipoze nou gen yon bouk for ki enprime yon sekans nimewo ant 0 a 10 nan entèval owaza (0–n segonn). Sèvi ak pwomès, ou bezwen chanje bouk sa a pou ke nimewo yo enprime nan sekans soti nan 0 a 10. Se konsa, si li pran 6 segonn enprime yon zewo ak 2 segonn enprime yon sèl, zewo a ta dwe enprime an premye, epi apre sa. dekont la pou enprime youn nan ap kòmanse.

Ak nan kou, nou pa itilize Async/Await oswa .sort pou rezoud pwoblèm sa a. Yon egzanp solisyon se nan fen an.

Fonksyon Async

Anplis de sa nan fonksyon async nan ES2017 (ES8) senplifye travay la nan travay ak pwomès yo. Mwen sonje ke fonksyon async travay "sou tèt" nan pwomès yo. Fonksyon sa yo pa reprezante konsèp kalitatif diferan. Fonksyon Async yo gen entansyon kòm yon altènativ a kòd ki sèvi ak pwomès.

Async/Await fè li posib pou òganize travay ak kòd asynchrone nan yon style synchrone.

Kidonk, konnen pwomès fè li pi fasil pou konprann prensip Async/Await.

sentaks

Nòmalman li konsiste de de mo kle: async ak tann. Premye mo a vire fonksyon an nan asynchrone. Fonksyon sa yo pèmèt itilize tann. Nan nenpòt lòt ka, lè l sèvi avèk fonksyon sa a pral jenere yon erè.

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

Async yo mete nan kòmansman an nan deklarasyon fonksyon an, ak nan ka a nan yon fonksyon flèch, ant "=" siy la ak parantèz yo.

Fonksyon sa yo ka mete nan yon objè kòm metòd oswa itilize nan yon deklarasyon klas.

// 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! Li vo sonje ke konstrukteur klas ak getters/setters pa ka asynchrone.

Semantik ak règ ekzekisyon

Fonksyon Async yo fondamantalman menm jan ak fonksyon JS estanda, men gen eksepsyon.

Kidonk, fonksyon async toujou retounen pwomès:

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

Espesyalman, fn retounen fisèl la alo. Oke, depi sa a se yon fonksyon asynchrone, valè fisèl la vlope nan yon pwomès lè l sèvi avèk yon konstrukteur.

Men yon konsepsyon altènatif san Async:

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

Nan ka sa a, pwomès la retounen "manyèlman". Yon fonksyon asynchrone toujou vlope nan yon nouvo pwomès.

Si valè retounen a se yon primitif, fonksyon async a retounen valè a lè li vlope li nan yon pwomès. Si valè retounen a se yon objè pwomès, rezolisyon li retounen nan yon nouvo pwomès.

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

Men, sa k ap pase si gen yon erè andedan yon fonksyon asynchrone?

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

Si li pa trete, foo() ap retounen yon pwomès ak rejè. Nan sitiyasyon sa a, yo pral retounen Promise.reject ki gen yon erè olye de Promise.resolve.

Fonksyon Async toujou bay yon pwomès, kèlkeswa sa yo retounen.

Fonksyon asynchrone pran poz sou chak ap tann.

Await afekte ekspresyon. Kidonk, si ekspresyon an se yon pwomès, fonksyon async la sispann jiskaske pwomès la akonpli. Si ekspresyon an se pa yon pwomès, li konvèti nan yon pwomès atravè Promise.resolve ak Lè sa a, ranpli.

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

Ak isit la se yon deskripsyon sou ki jan fonksyon an fn travay.

  • Apre w fin rele l, premye liy lan konvèti soti nan konst a = tann 9; in const a = tann Promise.resolve(9);.
  • Apre w fin itilize Await, ekzekisyon fonksyon an sispann jiskaske yon vin valè li (nan sitiyasyon aktyèl la li se 9).
  • delayAndGetRandom(1000) mete yon poz ekzekisyon fonksyon fn la jiskaske li konplete tèt li (apre 1 segonn). Sa a efektivman sispann fonksyon an fn pou 1 segonn.
  • delayAndGetRandom(1000) atravè rezolisyon retounen yon valè o aza, ki se Lè sa a, asiyen nan varyab la b.
  • Oke, ka a ak varyab c sanble ak ka a ak varyab a. Apre sa, tout bagay sispann pou yon segond, men kounye a delayAndGetRandom(1000) retounen anyen paske li pa obligatwa.
  • Kòm yon rezilta, valè yo kalkile lè l sèvi avèk fòmil a + b * c. Rezilta a vlope nan yon pwomès lè l sèvi avèk Promise.resolve epi li retounen nan fonksyon an.

Poz sa yo ka okoumansman de dèlko nan ES6, men gen yon bagay nan li rezon ou yo.

Rezoud pwoblèm nan

Oke, kounye a ann gade nan solisyon an nan pwoblèm nan mansyone pi wo a.

Fonksyon finishMyTask itilize Await pou tann rezilta operasyon yo tankou queryDatabase, sendEmail, logTaskInFile, ak lòt moun. Si w konpare solisyon sa a ak yon sèl kote pwomès yo te itilize, resanblans yo ap vin evidan. Sepandan, vèsyon Async/Await la anpil senplifye tout konpleksite sentaktik yo. Nan ka sa a, pa gen okenn gwo kantite callbacks ak chenn tankou .then/.catch.

Isit la se yon solisyon ak pwodiksyon an nan nimewo, gen de opsyon.

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

Ak isit la se yon solisyon lè l sèvi avèk fonksyon async.

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

Pwosesis erè

Erè ki pa okipe yo vlope nan yon pwomès rejte. Sepandan, fonksyon async ka itilize try/catch pou okipe erè synchrone.

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() se yon fonksyon asynchrone ki swa reyisi ("nimewo pafè") oswa echwe ak yon erè ("Padon, nimewo twò gwo").

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

Depi egzanp ki anwo a espere ke canRejectOrReturn egzekite, pwòp echèk li pral lakòz ekzekisyon blòk la trape. Kòm yon rezilta, fonksyon foo a pral fini ak swa endefini (lè pa gen anyen ki retounen nan blòk la eseye) oswa ak yon erè kenbe. Kòm yon rezilta, fonksyon sa a pa pral echwe paske eseye / trape a pral okipe fonksyon an foo tèt li.

Men yon lòt egzanp:

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

Li vo peye atansyon sou lefèt ke nan egzanp lan, canRejectOrReturn retounen soti nan foo. Foo nan ka sa a swa fini ak yon nimewo pafè oswa retounen yon Erè ("Padon, nimewo twò gwo"). Blòk trape an p ap janm egzekite.

Pwoblèm lan se ke foo retounen pwomès la te pase nan canRejectOrReturn. Se konsa, solisyon an nan foo vin solisyon an nan canRejectOrReturn. Nan ka sa a, kòd la pral konpoze de sèlman de liy:

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

Men sa k ap pase si w itilize tann epi retounen ansanm:

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

Nan kòd ki anwo a, foo pral sòti avèk siksè ak tou de yon nimewo pafè ak yon erè kenbe. Pa pral gen okenn refi isit la. Men foo ap retounen ak canRejectOrReturn, pa ak undefined. Ann asire w ke sa a lè w retire liy retounen await canRejectOrReturn():

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

Erè komen ak enkonvenyans

Nan kèk ka, lè l sèvi avèk Async/Await ka mennen nan erè.

Bliye tann

Sa rive byen souvan - mo kle await la bliye anvan pwomès la:

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

Kòm ou ka wè, pa gen okenn tann oswa retounen nan kòd la. Se poutèt sa foo toujou sòti ak undefined san yon reta 1 segonn. Men, pwomès la pral akonpli. Si li voye yon erè oswa yon rejè, yo pral rele UnhandledPromiseRejectionWarning.

Fonksyon Async nan Callbacks

Fonksyon Async yo byen souvan itilize nan .map oswa .filter kòm callbacks. Yon egzanp se fonksyon fetchPublicReposCount(username), ki retounen kantite depo louvri sou GitHub. Ann di gen twa itilizatè ki gen mezi nou bezwen. Men kòd pou travay sa a:

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

Nou bezwen kont ArfatSalman, octocat, norvig. Nan ka sa a nou fè:

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

Li vo peye atansyon sou Await nan .map callback la. Isit la konte se yon etalaj de pwomès, ak .map se yon apèl anonim pou chak itilizatè espesifye.

Twò konsistan itilize tann

Ann pran kòd sa a kòm yon egzanp:

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

Isit la nimewo repo a mete nan varyab konte a, Lè sa a, nimewo sa a ajoute nan etalaj la konte. Pwoblèm nan ak kòd la se ke jiskaske done premye itilizatè a rive soti nan sèvè a, tout itilizatè ki vin apre yo pral nan mòd sibstiti. Kidonk, se sèlman yon itilizatè trete nan yon moman.

Si, pou egzanp, li pran apeprè 300 ms pou trete yon itilizatè, Lè sa a, pou tout itilizatè li se deja yon dezyèm tan ki pase lineyè depann sou kantite itilizatè yo. Men, depi jwenn kantite repo pa depann youn ak lòt, pwosesis yo ka paralelize. Sa mande pou travay ak .map ak 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 resevwa yon seri pwomès kòm opinyon epi retounen yon pwomès. Lèt la, apre tout pwomès nan etalaj la fin ranpli oswa nan premye rejè a, fini. Li ka rive ke yo tout pa kòmanse an menm tan an - yo nan lòd yo asire kòmanse similtane, ou ka itilize p-map.

Konklizyon

Fonksyon Async yo ap vin de pli zan pli enpòtan pou devlopman. Oke, pou itilize adaptasyon nan fonksyon async, ou ta dwe itilize Iteratè Async. Yon pwomotè JavaScript ta dwe byen vèrs nan sa a.

Skillbox rekòmande:

Sous: www.habr.com

Add nouvo kòmantè