Ikus dezagun Async/Await JavaScript-en adibideak erabiliz

Artikuluaren egileak Javascript-en Async/Await-en adibideak aztertzen ditu. Orokorrean, Async/Await kode asinkronoa idazteko modu erosoa da. Ezaugarri hau agertu baino lehen, halako kodea idazten zen deiak eta promesak erabiliz. Jatorrizko artikuluaren egileak Async/Await-en abantailak agerian uzten ditu hainbat adibide aztertuz.

Gogoratzen dugu: "Habr" irakurle guztientzat - 10 errubloko deskontua "Habr" promozio-kodea erabiliz Skillbox-eko edozein ikastarotan izena ematean.

Skillbox-ek gomendatzen du: Hezkuntza online ikastaroa "Java garatzailea".

Atzeradeian

Callback funtzio bat da, zeinaren deia mugagabe atzeratzen den. Aurretik, emaitza berehala lortu ezin zen kode-eremuetan erabiltzen ziren deiak.

Hona hemen Node.js-en fitxategi bat modu asinkronoan irakurtzeko adibide bat:

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

Arazoak sortzen dira hainbat eragiketa asinkrono aldi berean egin behar dituzunean. Imajina dezagun eszenatoki hau: eskaera bat egiten zaio Arfat erabiltzaileen datu-baseari, bere profile_img_url eremua irakurri eta someserver.com zerbitzaritik irudi bat deskargatu behar duzu.
Deskargatu ondoren, irudia beste formatu batera bihurtzen dugu, PNGtik JPEGra adibidez. Bihurketa arrakastatsua izan bada, gutun bat bidaltzen da erabiltzailearen posta elektronikora. Ondoren, gertaerari buruzko informazioa transformations.log fitxategian sartzen da, data adieraziz.

Merezi du kodearen azken zatian deien gainjartzeari eta }) kopuru handiari erreparatzea. Callback Hell edo Pyramid of Doom deitzen da.

Metodo honen desabantailak agerikoak dira:

  • Kode hau irakurtzen zaila da.
  • Akatsak kudeatzea ere zaila da, eta horrek askotan kodearen kalitate txarra dakar.

Arazo hau konpontzeko, promesak gehitu ziren JavaScript-era. Deien itzulketen habia sakona .then hitzarekin ordezkatzeko aukera ematen dute.

Promesen alderdi positiboa da kodea askoz hobeto irakurtzen dutela, goitik behera ezkerretik eskuinera baino. Hala ere, promesek ere baditu bere arazoak:

  • .gero asko gehitu behar dituzu.
  • Try/catch-en ordez, .catch erabiltzen da errore guztiak kudeatzeko.
  • Begizta batean hainbat promesarekin lan egitea ez da beti komenigarria; kasu batzuetan, kodea zaildu egiten dute.

Hona hemen azken puntuaren esanahia erakutsiko duen arazo bat.

Demagun 0tik 10era bitarteko zenbaki-segida bat ausazko tarteetan (0-n segundotan) inprimatzen duen for begizta bat dugula. Promesak erabiliz, begizta hau aldatu behar duzu zenbakiak 0tik 10era sekuentzian inprimatzeko. Beraz, zero bat inprimatzeko 6 segundo behar badituzu eta bat inprimatzeko, zeroa inprimatu behar da lehenik, eta gero. bat inprimatzeko atzerako kontaketa hasiko da.

Eta noski, ez dugu Async/Await edo .sort erabiltzen arazo hau konpontzeko. Soluzio adibide bat amaieran dago.

Funtzio asinkronikoak

ES2017-n (ES8) funtzio asinkronikoak gehitzeak promesekin lan egiteko zeregina erraztu zuen. Funtzio asinkronikoak promesen "gainean" funtzionatzen dutela ohartzen naiz. Funtzio hauek ez dituzte kontzeptu kualitatiboki desberdinak adierazten. Funtzio asinkronikoak promesak erabiltzen dituen kodearen alternatiba gisa pentsatuta daude.

Async/Await-ek kode asinkronoarekin lana estilo sinkronoan antolatzea ahalbidetzen du.

Horrela, promesak ezagutzeak Async/Await-en printzipioak ulertzea errazten du.

sintaxia

Normalean bi gako-hitzek osatzen dute: async eta itxaron. Lehenengo hitzak funtzioa asinkrono bihurtzen du. Horrelako funtzioek await erabiltzeko aukera ematen dute. Beste edozein kasutan, funtzio hau erabiltzeak errore bat sortuko du.

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

Async funtzioaren adierazpenaren hasieran txertatzen da, eta gezi-funtzioaren kasuan, “=” zeinuaren eta parentesi artean.

Funtzio hauek objektu batean jar daitezke metodo gisa edo klase deklarazio batean erabil daitezke.

// 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! Komeni da gogoratzea klase-eraikitzaileak eta getters/setter-ak ezin direla asinkronoak izan.

Semantika eta exekuzio-arauak

Funtzio asinkronikoak JS funtzio estandarren antzekoak dira funtsean, baina salbuespenak daude.

Horrela, funtzio asinkronikoak beti itzultzen ditu promesak:

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

Zehazki, fn-k kaixo katea itzultzen du. Beno, funtzio asinkrono bat denez, katearen balioa promesa batean biltzen da eraikitzaile bat erabiliz.

Hona hemen diseinu alternatibo bat Async gabe:

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

Kasu honetan, promesa "eskuz" itzultzen da. Funtzio asinkrono bat beti dago promesa berri batean bilduta.

Itzultzeko balioa primitiboa bada, funtzio asinkronikoak balioa itzultzen du promesa batean bilduz. Itzultzeko balioa promesa objektu bat bada, bere ebazpena promesa berri batean itzultzen da.

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

Baina zer gertatzen da funtzio asinkrono baten barruan errore bat badago?

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

Prozesatzen ez bada, foo()-k promes bat itzuliko du errefusarekin. Egoera honetan, errore bat duen Promise.reject itzuliko da Promise.resolve-ren ordez.

Funtzio asinkronikoek promesa bat ateratzen dute beti, itzultzen dena edozein dela ere.

Funtzio asinkronoak itxaron bakoitzean pausatzen dira.

Itxaron esapideei eragiten die. Beraz, adierazpena promesa bat bada, funtzio asinkronikoa eten egiten da promesa bete arte. Adierazpena promesa bat ez bada, promesa bihurtzen da Promise.resolve bidez eta gero osatu.

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

Eta hona hemen fn funtzioak funtzionatzen duen deskribapena.

  • Deitu ondoren, lehen lerroa konst a = await 9-tik bihurtzen da; in const a = itxaron Promise.esolve(9);.
  • Await erabili ondoren, funtzioaren exekuzioa eten egiten da a-k bere balioa lortu arte (uneko egoeran 9 da).
  • delayAndGetRandom(1000) fn funtzioaren exekuzioa eten egiten du bere burua amaitu arte (segundo 1 ondoren). Honek fn funtzioa 1 segundoz gelditzen du.
  • delayAndGetRandom(1000) resolve bidez ausazko balio bat itzultzen du, gero b aldagaiari esleitzen zaiona.
  • Bada, c aldagaiaren kasua a aldagaiaren kasuaren antzekoa da. Horren ondoren, dena gelditzen da segundo batez, baina orain delayAndGetRandom(1000) ez du ezer itzultzen, beharrezkoa ez delako.
  • Ondorioz, balioak a + b * c formula erabiliz kalkulatzen dira. Emaitza promesa batean biltzen da Promise.resolve erabiliz eta funtzioak itzultzen du.

Eten hauek ES6-ko sorgailuak gogora ekar ditzakete, baina badago zerbait zure arrazoiak.

Arazoa konpontzea

Bada, orain ikus dezagun goian aipatutako arazoaren konponbidea.

finishMyTask funtzioak Itxaron erabiltzen du queryDatabase, sendEmail, logTaskInFile eta beste eragiketen emaitzen zain egoteko. Soluzio hau promesak erabiltzen zirenarekin alderatzen baduzu, antzekotasunak nabariak izango dira. Hala ere, Async/Await bertsioak konplexutasun sintaktiko guztiak errazten ditu. Kasu honetan, ez dago .then/.catch bezalako itzulera eta kate kopuru handirik.

Hona hemen zenbakien irteera duen irtenbide bat, bi aukera daude.

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

Eta hona hemen funtzio asinkronikoak erabiliz irtenbide bat.

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

Errore bat prozesatzen

Kudeatu gabeko akatsak baztertutako promesa batean biltzen dira. Hala ere, funtzio asinkronikoek try/catch erabil dezakete akatsak modu sinkronoan kudeatzeko.

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() funtzio asinkrono bat da («zenbaki perfektua») edo errore batekin huts egiten duena («Barkatu, zenbakia handiegia»).

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

Goiko adibidean canRejectOrReturn exekutatzea espero denez, bere hutsegiteak catch blokea exekutatu egingo du. Ondorioz, foo funtzioa undefined (try blokean ezer itzultzen ez denean) edo errore batekin amaituko da. Ondorioz, funtzio honek ez du huts egingo try/catch-ek foo funtzioa bera kudeatuko duelako.

Hona hemen beste adibide bat:

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

Merezi du arreta jartzea adibidean, canRejectOrReturn foo-tik itzultzen dela. Foo kasu honetan zenbaki perfektu batekin amaitzen da edo Errore bat ematen du ("Barkatu, zenbakia handiegia"). Catch blokea ez da inoiz exekutatuko.

Arazoa da fook canRejectOrReturn-etik emandako promesa itzultzen duela. Beraz, foo-ren irtenbidea canRejectOrReturn-en irtenbidea bihurtzen da. Kasu honetan, kodea bi lerro bakarrik izango da:

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

Hona hemen zer gertatzen den itxaron eta elkarrekin itzultzen baduzu:

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

Goiko kodean, foo arrakastaz aterako da zenbaki perfektu batekin eta errore batekin. Hemen ez da ukorik izango. Baina foo canRejectOrReturn-ekin itzuliko da, ez undefined-ekin. Ziurta dezagun hau return await canRejectOrReturn() lerroa kenduz:

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

Ohiko akatsak eta hutsuneak

Zenbait kasutan, Async/Await erabiltzeak akatsak sor ditzake.

Zain ahaztuta

Askotan gertatzen da hori - itxaron gako-hitza ahazten da promesaren aurretik:

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

Ikus dezakezunez, ez dago itxaron edo itzulerarik kodean. Beraz, foo beti irteten da undefined-ekin 1 segundoko atzerapenik gabe. Baina promesa beteko da. Errore bat edo errefusa ematen badu, UnhandledPromiseRejectionWarning deituko da.

Funtzio asinkronikoak dei-itzulketetan

Async funtzioak sarritan erabiltzen dira .map edo .filter-en itzulera gisa. Adibide bat fetchPublicReposCount(erabiltzaile-izena) funtzioa da, GitHub-en irekitako biltegien kopurua itzultzen duena. Demagun hiru erabiltzaile daudela zeinen neurketak behar ditugun. Hona hemen zeregin honen kodea:

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

ArfatSalman, octocat, norvig kontuak behar ditugu. Kasu honetan egiten dugu:

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

Merezi du arreta jartzea .map-en Await-i. Hemen counts promesa sorta bat da, eta .map zehaztutako erabiltzaile bakoitzarentzat dei anonimo bat da.

Await-en erabilera koherenteegia

Har dezagun kode hau adibide gisa:

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

Hemen repo zenbakia zenbaketa aldagaian jartzen da, gero zenbaki hau zenbaketen arrayra gehitzen da. Kodearen arazoa zerbitzaritik lehen erabiltzailearen datuak iritsi arte, hurrengo erabiltzaile guztiak egonean moduan egongo dira. Horrela, erabiltzaile bakarra prozesatzen da aldi berean.

Adibidez, erabiltzaile bat prozesatzeko 300 ms inguru behar badira, erabiltzaile guztientzat segundo bat da jada; linealki emandako denbora erabiltzaile kopuruaren araberakoa da. Baina repo kopurua lortzea bata bestearen menpe ez dagoenez, prozesuak paralelizatu daitezke. Horretarako .map eta Promise.all-ekin lan egin behar da:

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

Promise.all-ek promes sorta bat jasotzen du sarrera gisa eta promesa bat itzultzen du. Azken hau, array-ko promesa guztiak bete ondoren edo lehen ezespenean, bete egiten da. Gerta daiteke denak aldi berean ez hastea; aldi berean abiaraztea bermatzeko, p-map erabil dezakezu.

Ondorioa

Funtzio asinkronikoak gero eta garrantzi handiagoa hartzen ari dira garapenerako. Beno, funtzio asinkronikoen erabilera egokitzeko, erabili beharko zenuke Iterador asinkronikoak. JavaScript garatzaile batek ondo jakin behar du horretan.

Skillbox-ek gomendatzen du:

Iturria: www.habr.com

Gehitu iruzkin berria