Биёед бо истифода аз мисолҳо ба Async/Await дар JavaScript назар кунем

Муаллифи мақола намунаҳои Async/Await-ро дар JavaScript баррасӣ мекунад. Умуман, Async/Await роҳи қулай барои навиштани рамзи асинхронӣ мебошад. Пеш аз пайдо шудани ин хусусият, чунин код бо истифода аз зангҳо ва ваъдаҳо навишта мешуд. Муаллифи мақолаи аслӣ бартариҳои Async/Await-ро тавассути таҳлили мисолҳои гуногун ошкор мекунад.

Мо ба шумо хотиррасон мекунем: барои ҳамаи хонандагони "Habr" - тахфифи 10 000 рубл ҳангоми номнавис шудан ба курсҳои Skillbox бо истифода аз рамзи таблиғотии "Habr".

Skillbox тавсия медиҳад: Курси онлайни таълимӣ "Таҳиягари Java".

дар іолати

Бозгашти занг функсияест, ки занги он ба таври номуайян ба таъхир меафтад. Пештар зангҳои бозпас дар он соҳаҳои код истифода мешуданд, ки натиҷаро фавран ба даст овардан мумкин набуд.

Ин аст мисоли мутолиаи асинхронии файл дар Node.js:

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

Вақте ки шумо бояд якбора якчанд амалиёти асинхронӣ иҷро кунед, мушкилот ба миён меоянд. Биёед ин сенарияро тасаввур кунем: ба базаи корбари Арфат дархост дода мешавад, шумо бояд майдони profil_img_url-и онро хонед ва тасвирро аз сервери someserver.com зеркашӣ кунед.
Пас аз зеркашӣ, мо тасвирро ба формати дигар табдил медиҳем, масалан аз PNG ба JPEG. Агар табдилдиҳӣ бомуваффақият анҷом дода шавад, ба почтаи электронии корбар мактуб фиристода мешавад. Баъдан, маълумот дар бораи ҳодиса ба файли transformations.log бо нишон додани сана ворид карда мешавад.

Ба такрори зангҳо ва шумораи зиёди }) дар қисми охири код диққат додан лозим аст. Онро Callback Hell ё Пирамидаи Дом меноманд.

Камбудиҳои ин усул равшананд:

  • Ин кодро хондан душвор аст.
  • Муносибати хатогиҳо низ душвор аст, ки аксар вақт ба сифати пасти код оварда мерасонад.

Барои ҳалли ин мушкилот, ваъдаҳо ба JavaScript илова карда шуданд. Онҳо ба шумо имкон медиҳанд, ки лонаҳои амиқи зангҳоро бо калимаи .then иваз кунед.

Ҷанбаи мусбати ваъдаҳо дар он аст, ки онҳо кодро аз боло ба поён, на аз чап ба рост, хеле беҳтар хондан мекунанд. Бо вуҷуди ин, ваъдаҳо инчунин мушкилоти худро доранд:

  • Ба шумо лозим меояд, ки бисьёр .баъда илова кунед.
  • Ба ҷои кӯшиш/сайд, .catch барои коркарди ҳамаи хатогиҳо истифода мешавад.
  • Кор бо ваъдаҳои сершумор дар як давр на ҳамеша қулай аст; дар баъзе мавридҳо онҳо кодро душвор мегардонанд.

Ана як масъалае, ки маънои нуктаи охиринро нишон медихад.

Фарз мекунем, ки мо ҳалқаи for дорем, ки пайдарпаии рақамҳоро аз 0 то 10 бо фосилаҳои тасодуфӣ (0–n сония) чоп мекунад. Бо истифода аз ваъдаҳо, шумо бояд ин давраро тағир диҳед, то рақамҳо бо пайдарпай аз 0 то 10 чоп карда шаванд. Ҳамин тавр, агар барои чопи як сифр 6 сония ва барои чопи як 2 сония лозим шавад, аввал сифр бояд чоп карда шавад ва сипас ақибшумории чопи он оғоз мешавад.

Ва албатта, мо барои ҳалли ин мушкилот Async/Await ё .sort истифода намебарем. Як ҳалли мисол дар охири аст.

Функсияҳои асинхронӣ

Илова кардани функсияҳои асинхронӣ дар ES2017 (ES8) вазифаи кор бо ваъдаҳоро содда кард. Ман қайд мекунам, ки функсияҳои асинкӣ аз ваъдаҳо "боло" кор мекунанд. Ин функсияҳо мафҳумҳои сифатан гуногунро ифода намекунанд. Функсияҳои асинхронӣ ҳамчун алтернатива ба коде пешбинӣ шудаанд, ки ваъдаҳоро истифода мебаранд.

Async/Await имкон медиҳад, ки кор бо коди асинхронӣ дар услуби синхронӣ ташкил карда шавад.

Ҳамин тариқ, донистани ваъдаҳо фаҳмидани принсипҳои Async/Await-ро осонтар мекунад.

наҳвӣ

Одатан он аз ду калимаи калидӣ иборат аст: асинхронӣ ва интизорӣ. Калимаи аввал функсияро ба асинхронӣ табдил медиҳад. Чунин функсияҳо имкон медиҳанд, ки интизор шаванд. Дар ҳама ҳолатҳои дигар, истифодаи ин функсия хатогӣ ба вуҷуд меорад.

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

Async дар ибтидои эъломияи функсия ва дар ҳолати функсияи тирча дар байни аломати "=" ва қавс ҷойгир карда мешавад.

Ин функсияҳоро метавон дар объект ҳамчун усул ҷойгир кард ё дар эъломияи синф истифода бурд.

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

НБ! Бояд дар хотир дошт, ки конструкторҳои синфҳо ва қабулкунандаҳо / танзимкунанда наметавонанд асинхронӣ бошанд.

Семантика ва қоидаҳои иҷро

Функсияҳои асинхронӣ асосан ба функсияҳои стандартии JS монанданд, аммо истисноҳо мавҷуданд.

Ҳамин тариқ, функсияҳои асинхронӣ ҳамеша ваъдаҳоро бармегардонанд:

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

Махсусан, fn сатри hello-ро бармегардонад. Хуб, азбаски ин функсияи асинхронӣ аст, арзиши сатр бо истифода аз созанда дар ваъда печонида мешавад.

Ин як тарҳи алтернативӣ бидуни Async аст:

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

Дар ин сурат ваъда «дастӣ» баргардонида мешавад. Функсияи асинхронӣ ҳамеша бо ваъдаи нав печонида мешавад.

Агар арзиши баргардонидани ибтидоӣ бошад, функсияи асинкӣ арзишро тавассути печонидани он дар ваъда бармегардонад. Агар арзиши бозгашт объекти ваъда бошад, ҳалли он дар ваъдаи нав баргардонида мешавад.

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

Аммо агар дар дохили функсияи асинхронӣ хатогӣ мавҷуд бошад, чӣ мешавад?

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

Агар он коркард нашавад, foo() ваъдаро бо радд бармегардонад. Дар ин ҳолат, ба ҷои Promise.resolve, Promise.reject, ки хато дорад, баргардонида мешавад.

Функсияҳои асинхронӣ ҳамеша ваъда медиҳанд, новобаста аз он ки баргардонида мешавад.

Функсияҳои асинхронӣ дар ҳар интизорӣ таваққуф мекунанд.

Интизорӣ ба ифодаҳо таъсир мерасонад. Ҳамин тавр, агар ифода ваъда бошад, функсияи асинхронӣ то иҷро шудани ваъда боздошта мешавад. Агар ифода ваъда набошад, он тавассути Promise.resolve ба ваъда табдил дода мешавад ва сипас анҷом дода мешавад.

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

Ва дар ин ҷо тавсифи он аст, ки функсияи fn чӣ гуна кор мекунад.

  • Пас аз даъват кардани он, сатри аввал аз const a = wait 9 табдил дода мешавад; дар const a = интизори Promise.resolve(9);.
  • Пас аз истифодаи Await, иҷрои функсия то ба даст овардани арзиши худ боздошта мешавад (дар ҳолати кунунӣ он 9 аст).
  • delayAndGetRandom(1000) иҷрои функсияи fn-ро то он даме, ки худ ба охир расад (пас аз 1 сония) таваққуф мекунад. Ин функсияи fn-ро барои 1 сония ба таври муассир қатъ мекунад.
  • delayAndGetRandom (1000) тавассути ҳалли арзиши тасодуфиро бармегардонад, ки он гоҳ ба тағирёбандаи b таъин карда мешавад.
  • Хуб, парванда бо тағирёбандаи c ба парвандаи тағирёбандаи a шабеҳ аст. Пас аз ин, ҳама чиз барои як сония қатъ мешавад, аммо ҳоло delayAndGetRandom(1000) чизе барнамегардонад, зеро он талаб карда намешавад.
  • Дар натиҷа, арзишҳо бо формулаи a + b * c ҳисоб карда мешаванд. Натиҷа бо истифода аз Promise.resolve бо ваъда печонида мешавад ва аз ҷониби функсия баргардонида мешавад.

Ин таваққуфҳо метавонанд генераторҳои ES6-ро ба хотир оранд, аммо дар он чизе ҳаст сабабҳои шумо.

Халли масъала

Хуб, акнун биёед ба ҳалли масъалаи дар боло зикршуда назар андозем.

Функсияи finishMyTask Await-ро барои интизории натиҷаҳои амалиётҳо ба монанди queryDatabase, sendEmail, logTaskInFile ва ғайра истифода мебарад. Агар шумо ин ҳалли худро бо он чизе, ки ваъдаҳо истифода мешуданд, муқоиса кунед, шабоҳатҳо аён хоҳанд шуд. Аммо, версияи Async/Await тамоми мураккабии синтаксисиро хеле содда мекунад. Дар ин ҳолат шумораи зиёди зангҳо ва занҷирҳо ба монанди .then/.catch вуҷуд надорад.

Ин аст як ҳалли бо баромади рақамҳо, ду вариант вуҷуд дорад.

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

Ва дар ин ҷо як роҳи ҳалли истифодаи функсияҳои асинкӣ аст.

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

Коркарди хато

Хатогиҳои ҳалнашуда бо ваъдаи радшуда печонида мешаванд. Аммо, функсияҳои асинхронӣ метавонанд кӯшиш/сайдро барои коркарди хатогиҳо ба таври синхронӣ истифода баранд.

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() функсияи асинхронӣ аст, ки ё бомуваффақият иҷро мешавад ("рақами комил") ё бо хатогӣ ноком мешавад ("Бубахшед, рақам хеле калон").

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

Азбаски мисоли боло иҷроиши canRejectOrReturn-ро интизор аст, нокомии худи он ба иҷрои блоки catch оварда мерасонад. Дар натиҷа, функсияи foo бо ё номуайян (ҳангоме ки дар блоки озмоиш чизе барнагаштааст) ё бо хатогӣ ба охир мерасад. Дар натиҷа, ин функсия ноком нахоҳад шуд, зеро try/catch худи функсияи foo-ро идора мекунад.

Инак як мисоли дигар:

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

Қобили таваҷҷӯҳ ба он аст, ки дар мисол, canRejectOrReturn аз foo баргардонида мешавад. Foo дар ин ҳолат ё бо рақами комил хотима меёбад ё хато бармегардонад (“Бубахшед, рақам хеле калон”). Блоки сайд ҳеҷ гоҳ иҷро намешавад.

Мушкилот дар он аст, ки foo ваъдаи аз canRejectOrReturn додашударо бармегардонад. Ҳамин тавр, ҳалли foo ҳалли canRejectOrReturn мегардад. Дар ин ҳолат, код танҳо аз ду сатр иборат хоҳад буд:

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

Ин аст, ки агар шумо интизор ва бозгаштро якҷоя истифода баред, чӣ мешавад:

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

Дар коди дар боло зикршуда, foo бо рақами комил ва хатои дастгиршуда бомуваффақият мебарояд. Дар ин ҷо ҳеҷ гуна радкунӣ вуҷуд нахоҳад дошт. Аммо foo бо canRejectOrReturn бармегардад, на бо номуайян. Биёед бо бартараф кардани сатри бозгашти canRejectOrReturn() боварӣ ҳосил кунем:

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

Хатогиҳои умумӣ ва хатогиҳо

Дар баъзе ҳолатҳо, истифодаи Async/Await метавонад ба хатогиҳо оварда расонад.

Фаромӯшшуда интизоранд

Ин бисёр вақт рӯй медиҳад - калимаи интизорӣ пеш аз ваъда фаромӯш мешавад:

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

Тавре ки шумо мебинед, дар код ягон интизорӣ ё бозгашт вуҷуд надорад. Аз ин рӯ, foo ҳамеша бо номуайян бидуни таъхири 1 сония мебарояд. Аммо ваъда ичро мешавад. Агар он хато ё рад кунад, он гоҳ UnhandledPromiseRejectionWarning даъват карда мешавад.

Функсияҳои асинхронӣ дар зангҳои бозгашт

Функсияҳои асинхронӣ аксар вақт дар .map ё .filter ҳамчун зангҳои бозгашт истифода мешаванд. Мисол функсияи fetchPublicReposCount(номи корбар) мебошад, ки шумораи анборҳои кушодаро дар GitHub бармегардонад. Фарз мекунем, ки се корбаре ҳастанд, ки ченакҳои онҳо ба мо лозиманд. Ин аст рамзи ин вазифа:

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

Ба мо ҳисобҳои АрфатСалман, октокат, норвиг лозим аст. Дар ин ҳолат мо мекунем:

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

Қобили таваҷҷуҳ ба Интизорӣ дар .map занги бозгашт аст. Дар ин ҷо ҳисоб як қатор ваъдаҳост ва .map занги беном барои ҳар як корбари муайяншуда мебошад.

Истифодаи аз ҳад зиёд пайваста интизор аст

Биёед ин кодро ҳамчун мисол гирем:

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

Дар ин ҷо рақами репо дар тағирёбандаи ҳисоб ҷойгир карда мешавад, пас ин рақам ба массиви ҳисобҳо илова карда мешавад. Мушкилоти рамз дар он аст, ки то аз сервер ворид шудани маълумоти корбари аввал, ҳамаи корбарони минбаъда дар ҳолати интизорӣ қарор хоҳанд дошт. Ҳамин тариқ, дар як вақт танҳо як корбар коркард карда мешавад.

Агар, масалан, барои коркарди як корбар тақрибан 300 мс лозим бошад, пас барои ҳамаи корбарон ин аллакай як сония аст; вақти сарфшуда ба таври хаттӣ аз шумораи корбарон вобаста аст. Аммо азбаски гирифтани шумораи репо аз ҳамдигар вобаста нест, равандҳоро метавон параллел кард. Барои ин кор бо .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 як қатор ваъдаҳоро ҳамчун вуруд қабул мекунад ва ваъдаро бармегардонад. Охирин, пас аз анҷом додани ҳама ваъдаҳо дар массив ё дар радди аввал анҷом дода мешавад. Мумкин аст, ки ҳамаи онҳо дар як вақт оғоз нашаванд - барои таъмини оғози ҳамзамон, шумо метавонед p-map-ро истифода баред.

хулоса

Функсияҳои асинхронӣ барои рушд аҳамияти бештар пайдо мекунанд. Хуб, барои истифодаи мутобиқсозии функсияҳои асинкӣ, шумо бояд истифода баред Итераторҳои асинхронӣ. Таҳиягари JavaScript бояд инро хуб донад.

Skillbox тавсия медиҳад:

Манбаъ: will.com

Илова Эзоҳ