Bheir sinn sùil air Async / Await ann an JavaScript a’ cleachdadh eisimpleirean

Bidh ùghdar an artaigil a’ sgrùdadh eisimpleirean de Async / Await ann an JavaScript. Gu h-iomlan, tha Async / Await na dhòigh goireasach air còd asyncronach a sgrìobhadh. Mus do nochd am feart seo, chaidh an còd sin a sgrìobhadh a’ cleachdadh fios air ais agus geallaidhean. Tha ùghdar an artaigil thùsail a’ nochdadh buannachdan Async/Await le bhith a’ dèanamh anailis air grunn eisimpleirean.

Tha sinn a ’cur nar cuimhne: airson a h-uile leughadair de "Habr" - lasachadh de 10 rubles nuair a chlàraicheas tu ann an cùrsa sam bith Skillbox a 'cleachdadh a' chòd adhartachaidh "Habr".

Tha Skillbox a’ moladh: Cùrsa foghlaim air-loidhne "Leasaiche Java".

callback

Is e gnìomh a th’ ann an callback far a bheil dàil air a’ ghairm gun chrìoch. Roimhe sin, chaidh fios air ais a chleachdadh anns na raointean còd sin far nach robh e comasach an toradh fhaighinn sa bhad.

Seo eisimpleir de bhith a’ leughadh faidhle ann an Node.js gu neo-chinnteach:

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

Bidh duilgheadasan ag èirigh nuair a dh’ fheumas tu grunn obrachaidhean asyncronach a dhèanamh aig an aon àm. Smaoinich sinn air an t-suidheachadh seo: thèid iarrtas a chuir gu stòr-dàta luchd-cleachdaidh Arfat, feumaidh tu an raon profile_img_url aige a leughadh agus ìomhaigh a luchdachadh sìos bhon t-seirbheisiche someserver.com.
Às deidh an luchdachadh sìos, tionndaidhidh sinn an ìomhaigh gu cruth eile, mar eisimpleir bho PNG gu JPEG. Ma bha an tionndadh soirbheachail, thèid litir a chuir gu post-d an neach-cleachdaidh. An ath rud, thèid fiosrachadh mun tachartas a chuir a-steach don fhaidhle transforms.log, a’ comharrachadh a’ chinn-latha.

Is fhiach aire a thoirt don ath-tharraing de ghairm air ais agus an àireamh mhòr de }) anns a’ phàirt mu dheireadh den chòd. Canar Callback Ifrinn no Pyramid of Doom ris.

Tha eas-bhuannachdan an dòigh seo follaiseach:

  • Tha an còd seo duilich a leughadh.
  • Tha e duilich cuideachd mearachdan a làimhseachadh, a bhios gu tric a’ leantainn gu droch chàileachd còd.

Gus an duilgheadas seo fhuasgladh, chaidh geallaidhean a chuir ri JavaScript. Leigidh iad leat am facal .then a chur an àite neadachadh domhainn de ghairm air ais.

Is e an taobh adhartach de gheallaidhean gu bheil iad a’ dèanamh a’ chòd gu math furasta a leughadh, bho mhullach gu bonn seach bho chlì gu deas. Ach, tha na duilgheadasan aca cuideachd aig geallaidhean:

  • Feumaidh tu tòrr .then a chur ris.
  • An àite feuchainn/glacadh, bithear a’ cleachdadh .catch airson gach mearachd a làimhseachadh.
  • Chan eil e an-còmhnaidh goireasach obrachadh le ioma gheallaidhean taobh a-staigh aon lùb; ann an cuid de chùisean, bidh iad a’ dèanamh a’ chòd iom-fhillte.

Seo duilgheadas a sheallas brìgh a’ phuing mu dheireadh.

Osbarr gu bheil lùb againn a bhios a’ clò-bhualadh sreath àireamhan bho 0 gu 10 aig amannan air thuaiream (0–n diogan). A 'cleachdadh geallaidhean, feumaidh tu an lùb seo atharrachadh gus am bi na h-àireamhan air an clò-bhualadh ann an òrdugh bho 0 gu 10. Mar sin, ma bheir e 6 diogan airson neoni agus 2 diogan a chlò-bhualadh, bu chòir an neoni a chlò-bhualadh an toiseach, agus an uairsin tòisichidh an cunntadh sìos airson clò-bhualadh.

Agus gu dearbh, chan eil sinn a 'cleachdadh Async/Await no .sort gus fuasgladh fhaighinn air an duilgheadas seo. Tha fuasgladh eisimpleir aig an deireadh.

Gnìomhan async

Le bhith a’ cur a-steach gnìomhan async ann an ES2017 (ES8) rinn sin sìmplidh air a’ ghnìomh a bhith ag obair le geallaidhean. Tha mi a’ toirt fa-near gu bheil gnìomhan async ag obair “air mullach” geallaidhean. Chan eil na gnìomhan sin a’ riochdachadh bun-bheachdan càileachdail eadar-dhealaichte. Tha gnìomhan Async an dùil mar roghainn eile an àite còd a chleachdas geallaidhean.

Tha Async/Await ga dhèanamh comasach obair a chuir air dòigh le còd asyncronach ann an stoidhle sioncronaich.

Mar sin, le bhith eòlach air geallaidhean ga dhèanamh nas fhasa prionnsapalan Async / Await a thuigsinn.

sheantansan

Mar as trice tha dà phrìomh fhacal ann: async agus feitheamh. Tha a’ chiad fhacal a’ tionndadh an gnìomh gu asyncronach. Tha gnìomhan mar seo a 'ceadachadh cleachdadh feitheamh. Ann an suidheachadh sam bith eile, cruthaichidh cleachdadh na gnìomh seo mearachd.

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

Tha Async air a chuir a-steach aig fìor thoiseach an dearbhadh gnìomh, agus a thaobh gnìomh saighead, eadar an soidhne “=” agus na bragan.

Faodar na gnìomhan sin a chuir ann an nì mar dhòighean no an cleachdadh ann an dearbhadh clas.

// 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! Is fhiach cuimhneachadh nach urrainn do luchd-togail clas agus luchd-faighinn / luchd-seallaidh a bhith asioncronach.

Semantics agus riaghailtean cur gu bàs

Tha gnìomhan Async gu ìre mhòr coltach ri gnìomhan àbhaisteach JS, ach tha eisgeachdan ann.

Mar sin, bidh gnìomhan async an-còmhnaidh a’ tilleadh gheallaidhean:

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

Gu sònraichte, bidh fn a 'tilleadh an t-sreath hello. Uill, leis gur e gnìomh asyncronach a tha seo, tha luach sreang air a phasgadh ann an gealladh a’ cleachdadh neach-togail.

Seo dealbhadh eile às aonais Async:

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

Anns a 'chùis seo, tha an gealladh air a thilleadh "le làimh". Tha gnìomh asyncronach an-còmhnaidh ceangailte ann an gealladh ùr.

Ma tha an luach tilleadh prìomhadail, tillidh an gnìomh async an luach le bhith ga phasgadh ann an gealladh. Ma tha an luach tilleadh na nì gealltanas, tha an rùn aige air a thilleadh ann an gealladh ùr.

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

Ach dè a thachras ma tha mearachd taobh a-staigh gnìomh asyncronach?

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

Mura tèid a phròiseasadh, tillidh foo() gealladh le diùltadh. Anns an t-suidheachadh seo, thèid Promise.reject anns a bheil mearachd a thilleadh an àite Promise.resolve.

Bidh gnìomhan Async an-còmhnaidh a’ toirt a-mach gealladh, ge bith dè a thèid a thilleadh.

Bidh gnìomhan asyncronach a’ stad air a h-uile feitheamh.

A 'feitheamh ri buaidhean abairtean. Mar sin, ma tha an abairt na ghealladh, tha an gnìomh async air a chuir dheth gus an tèid an gealladh a choileanadh. Mura h-eil an abairt na ghealladh, thèid a thionndadh gu gealladh tro Promise.resolve agus an uairsin a chrìochnachadh.

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

Agus seo cunntas air mar a tha an gnìomh fn ag obair.

  • An dèidh a ghairm, tha a 'chiad loidhne air a thionndadh bho const a = feitheamh 9; in const a = feitheamh Promise.resolve(9);.
  • Às deidh dha Await a chleachdadh, tha coileanadh gnìomh air a chuir dheth gus am faigh e a luach (anns an t-suidheachadh làithreach tha e 9).
  • dàilAndGetRandom (1000) a’ stad buileachadh a’ ghnìomh fn gus an cuir e crìoch air fhèin (às deidh 1 diog). Bidh seo gu h-èifeachdach a’ stad an gnìomh fn airson 1 diog.
  • bidh dàilAndGetRandom (1000) tro fhuasgladh a’ tilleadh luach air thuaiream, a tha an uairsin air a shònrachadh don chaochladair b.
  • Uill, tha a’ chùis le caochladair c coltach ris a’ chùis le caochladair a. Às deidh sin, stadaidh a h-uile càil airson diog, ach a-nis chan eil dàil AndGetRandom (1000) a ’tilleadh dad leis nach eil feum air.
  • Mar thoradh air an sin, tha na luachan air an tomhas leis an fhoirmle a + b * c. Tha an toradh air a phasgadh ann an gealladh a’ cleachdadh Promise.resolve agus air a thilleadh leis a’ ghnìomh.

Is dòcha gu bheil na stadan sin mar chuimhneachan air gineadairean ann an ES6, ach tha rudeigin ris na h-adhbharan agad.

Fuasgladh na trioblaid

Uill, a-nis leig dhuinn sùil a thoirt air am fuasgladh don duilgheadas a chaidh ainmeachadh gu h-àrd.

Bidh an gnìomh crìochnachaidhMyTask a’ cleachdadh Await gus feitheamh ri toraidhean gnìomhachd leithid queryDatabase, sendEmail, logTaskInFile, agus feadhainn eile. Ma nì thu coimeas eadar am fuasgladh seo agus am fuasgladh far an deach geallaidhean a chleachdadh, bidh na rudan coltach ri chèile follaiseach. Ach, tha an dreach Async / Await gu mòr a’ sìmpleachadh a h-uile iom-fhillteachd syntactic. Anns a 'chùis seo, chan eil àireamh mhòr de callbacks agus slabhraidhean mar .then/.catch.

Seo fuasgladh le toradh àireamhan, tha dà roghainn ann.

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

Agus seo fuasgladh a’ cleachdadh gnìomhan async.

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

Giullachd mearachd

Tha mearachdan gun làimhseachadh air am pasgadh ann an gealladh a chaidh a dhiùltadh. Ach, faodaidh gnìomhan async feuchainn / glacadh gus mearachdan a làimhseachadh gu sioncronaich.

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

Is e gnìomh asyncronach a th’ ann an canRejectOrReturn () a shoirbhicheas leis (“àireamh foirfe”) no a dh’ fhailicheas le mearachd (“Duilich, tha an àireamh ro mhòr”).

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

Leis gu bheil an eisimpleir gu h-àrd an dùil canRejectOrReturn a chuir gu bàs, thig am fàiligeadh aige fhèin gu buil a’ bhloc glacaidh. Mar thoradh air an sin, thig an gnìomh foo gu crìch le aon chuid neo-mhìnichte (nuair nach eil dad air a thilleadh sa bhloc feuchainn) no le mearachd air a ghlacadh. Mar thoradh air an sin, cha bhith an gnìomh seo a’ fàilligeadh oir làimhsichidh an feuchainn / glacadh an gnìomh foo fhèin.

Seo eisimpleir eile:

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

'S fhiach toirt fa-near gu bheil anns an eisimpleir urrainn RejectOrReturn a thilleadh bho foo. Bidh Foo sa chùis seo an dàrna cuid a’ tighinn gu crìch le àireamh foirfe no a’ tilleadh Mearachd (“Duilich, tha an àireamh ro mhòr”). Cha tèid am bloc glacaidh a chuir gu bàs gu bràth.

Is e an duilgheadas a th’ ann gu bheil foo a’ tilleadh a’ gheallaidh a fhuaireadh bho canRejectOrReturn. Mar sin bidh am fuasgladh dha foo na fhuasgladh airson canRejectOrReturn. Anns a 'chùis seo, cha bhi an còd ach dà loidhne:

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

Seo na thachras ma chleachdas tu feitheamh agus tilleadh còmhla:

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

Anns a 'chòd gu h-àrd, fàgaidh foo gu soirbheachail le àireamh foirfe agus mearachd air a ghlacadh. Cha bhi diùltadh an seo. Ach tillidh foo le canRejectOrReturn, chan ann le neo-mhìnichte. Feuch an dèan sinn cinnteach à seo le bhith a’ toirt air falbh an loidhne feitheamh canRejectOrReturn():

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

Mearachdan agus lochdan cumanta

Ann an cuid de chùisean, faodaidh cleachdadh Async / Await leantainn gu mearachdan.

Air dhearmad feitheamh

Bidh seo a’ tachairt gu math tric - tha am prìomh fhacal feitheamh air a dhìochuimhneachadh ron ghealladh:

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

Mar a chì thu, chan eil feitheamh no tilleadh sa chòd. Mar sin bidh foo an-còmhnaidh a’ falbh le neo-mhìnichte gun dàil 1 diog. Ach bidh an gealladh air a choileanadh. Ma thilgeas e mearachd no diùltadh, an uairsin thèid UnhandledPromiseRejectionWarning a ghairm.

Gnìomhan Async ann an Callbacks

Bidh gnìomhan Async gu math tric air an cleachdadh ann an .map no .filter mar chùl-ghairm. Is e eisimpleir an gnìomh fetchPublicReposCount (ainm-cleachdaidh), a thilleas an àireamh de stòran fosgailte air GitHub. Canaidh sinn gu bheil triùir luchd-cleachdaidh aig a bheil na meatrach a dh’ fheumas sinn. Seo an còd airson na h-obrach seo:

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

Feumaidh sinn cunntasan ArfatSalman, octocat, norvig. Anns a 'chùis seo bidh sinn a' dèanamh:

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

'S fhiach pàigheadh ​​​​aire gu Await ann an callback .map. An seo tha grunn gheallaidhean ann an cunntadh, agus tha .map na ghairm air ais gun urra airson gach neach-cleachdaidh ainmichte.

Cleachdadh ro chunbhalach a’ feitheamh

Gabhamaid an còd seo mar eisimpleir:

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

An seo tha an àireamh repo air a chuir anns a’ chaochladair cunntais, agus an uairsin thèid an àireamh seo a chur ris an raon cunntais. Is e an duilgheadas leis a’ chòd, gus an ruig dàta a’ chiad neach-cleachdaidh bhon fhrithealaiche, gum bi a h-uile neach-cleachdaidh às deidh sin ann am modh cùl-taic. Mar sin, chan eil ach aon neach-cleachdaidh air a phròiseasadh aig aon àm.

Ma bheir e, mar eisimpleir, timcheall air 300 ms airson aon neach-cleachdaidh a phròiseasadh, an uairsin airson a h-uile neach-cleachdaidh tha e mar diog mar-thà; bidh an ùine a thèid a chaitheamh gu sreathach an urra ris an àireamh de luchd-cleachdaidh. Ach leis nach eil a bhith a’ faighinn an àireamh de repo an urra ri chèile, faodar na pròiseasan a cho-thaobhadh. Feumaidh seo obrachadh le .map agus Promise.all:

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

Bidh Promise.all a’ faighinn sreath de gheallaidhean mar chur-a-steach agus a’ tilleadh gealladh. Tha an tè mu dheireadh, an dèidh a h-uile gealladh anns an raon a chrìochnachadh no aig a 'chiad diùltadh, air a chrìochnachadh. Faodaidh e tachairt nach tòisich iad uile aig an aon àm - gus dèanamh cinnteach gun tòisich iad aig an aon àm, faodaidh tu p-mapa a chleachdadh.

co-dhùnadh

Tha gnìomhan Async a’ sìor fhàs cudromach airson leasachadh. Uill, airson cleachdadh atharrachail de ghnìomhan async, bu chòir dhut a chleachdadh Iterators Async. Bu chòir do leasaiche JavaScript a bhith eòlach air seo.

Tha Skillbox a’ moladh:

Source: www.habr.com

Cuir beachd ann