Ayo goleki Async/Await ing JavaScript nggunakake conto

Penulis artikel mriksa conto Async/Await ing JavaScript. Sakabèhé, Async/Await minangka cara sing trep kanggo nulis kode asinkron. Sadurunge fitur iki muncul, kode kasebut ditulis nggunakake callback lan janji. Penulis artikel asli nyritakake kaluwihan Async/Await kanthi nganalisa macem-macem conto.

Kita ngelingake: kanggo kabeh sing maca "Habr" - diskon 10 rubel nalika ndhaptar kursus Skillbox nggunakake kode promosi "Habr".

Skillbox nyaranake: Kursus online pendidikan "pangembang jawa".

callback

Callback minangka fungsi sing telpon ditundha tanpa wates. Sadurunge, callback digunakake ing wilayah kode sing asil ora bisa langsung dipikolehi.

Iki minangka conto maca file sing ora sinkron ing Node.js:

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

Masalah muncul nalika sampeyan kudu nindakake sawetara operasi bedo bebarengan. Coba bayangake skenario iki: panjaluk digawe menyang database pangguna Arfat, sampeyan kudu maca kolom profile_img_url lan download gambar saka server someserver.com.
Sawise ngundhuh, kita ngowahi gambar menyang format liyane, contone saka PNG menyang JPEG. Yen konversi sukses, layang dikirim menyang email pangguna. Sabanjure, informasi babagan acara kasebut dilebokake ing file transformations.log, sing nuduhake tanggal.

Iku worth mbayar manungsa waé menyang tumpang tindih callbacks lan nomer akeh }) ing bagéan pungkasan saka kode. Iki diarani Callback Hell utawa Pyramid of Doom.

Kerugian metode iki jelas:

  • Kode iki angel diwaca.
  • Sampeyan uga angel kanggo nangani kesalahan, sing asring nyebabake kualitas kode sing ora apik.

Kanggo ngatasi masalah iki, janji ditambahake menyang JavaScript. Padha ngidini sampeyan ngganti nesting jero callbacks karo tembung .banjur.

Aspek positif saka janji yaiku supaya kode kasebut bisa diwaca luwih apik, saka ndhuwur nganti ngisor tinimbang saka kiwa menyang tengen. Nanging, janji uga duwe masalah:

  • Sampeyan kudu nambah akeh .banjur.
  • Tinimbang nyoba / nyekel, .nyekel digunakake kanggo nangani kabeh kasalahan.
  • Nggarap macem-macem janji ing siji daur ulang ora tansah trep; ing sawetara kasus, padha complicate kode.

Punika masalah sing bakal nuduhake makna saka titik pungkasan.

Contone, kita duwe daur ulang kanggo sing nyithak urutan nomer saka 0 nganti 10 kanthi interval acak (0-n detik). Nggunakake janji, sampeyan kudu ngganti daur ulang iki supaya angka kasebut dicithak kanthi urutan saka 0 nganti 10. Dadi, yen butuh 6 detik kanggo nyithak nol lan 2 detik kanggo nyithak siji, nul kudu dicithak dhisik, banjur countdown kanggo printing siji bakal miwiti.

Lan mesthi, kita ora nggunakake Async / Enteni utawa .sort kanggo ngatasi masalah iki. Solusi conto ing pungkasan.

Fungsi Async

Penambahan fungsi async ing ES2017 (ES8) nyederhanakake tugas nggarap janji. Aku nyathet yen fungsi async bisa "ing ndhuwur" janji. Fungsi kasebut ora nggambarake konsep sing beda-beda sacara kualitatif. Fungsi Async dimaksudake minangka alternatif kanggo kode sing nggunakake janji.

Async / Enteni ndadekake iku bisa kanggo ngatur karya karo kode bedo ing gaya sinkron.

Mangkono, ngerti janji nggawe luwih gampang mangertos prinsip Async/Await.

sintaks

Biasane kasusun saka rong tembung kunci: async lan ngenteni. Tembung pisanan ngowahi fungsi kasebut dadi asinkron. Fungsi kasebut ngidini panggunaan ngenteni. Ing kasus liyane, nggunakake fungsi iki bakal nggawe kesalahan.

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

Async dilebokake ing wiwitan deklarasi fungsi, lan ing kasus fungsi panah, antarane tandha "=" lan tanda kurung.

Fungsi kasebut bisa diselehake ing obyek minangka metode utawa digunakake ing deklarasi kelas.

// 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! Iku worth ngelingi sing konstruktor kelas lan getter / setter ora bisa asynchronous.

Semantik lan aturan eksekusi

Fungsi Async Sejatine padha karo fungsi JS standar, nanging ana pangecualian.

Dadi, fungsi async tansah ngasilake janji:

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

Khusus, fn ngasilake string hello. Ya, amarga iki minangka fungsi asinkron, nilai senar dibungkus janji nggunakake konstruktor.

Mangkene desain alternatif tanpa Async:

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

Ing kasus iki, janji bali "kanthi manual". Fungsi asynchronous tansah kebungkus janji anyar.

Yen nilai bali minangka primitif, fungsi async ngasilake nilai kasebut kanthi mbungkus janji. Yen nilai bali minangka obyek janji, resolusi kasebut bali menyang janji anyar.

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

Nanging apa sing kedadeyan yen ana kesalahan ing fungsi asinkron?

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

Yen ora diproses, foo () bakal bali janji karo larangan. Ing kahanan iki, Promise.reject ngemot kesalahan bakal bali tinimbang Promise.resolve.

Fungsi Async tansah ngasilake janji, preduli saka apa sing bali.

Fungsi asinkron ngaso saben ngenteni.

Enteni mengaruhi ekspresi. Dadi, yen ekspresi kasebut minangka janji, fungsi async ditundha nganti janji kasebut kawujud. Yen ekspresi dudu janji, diowahi dadi janji liwat Promise.resolve banjur rampung.

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

Lan iki minangka katrangan babagan cara fungsi fn.

  • Sawise nelpon, baris pisanan diowahi saka const a = ngenteni 9; ing const a = ngenteni Promise.resolve(9);.
  • Sawise nggunakake Enteni, eksekusi fungsi dilereni soko tugas nganti a entuk nilai (ing kahanan saiki iku 9).
  • delayAndGetRandom(1000) ngaso eksekusi fungsi fn nganti rampung dhewe (sawise 1 detik). Iki kanthi efektif mungkasi fungsi fn sajrone 1 detik.
  • delayAndGetRandom (1000) liwat mutusake masalah ngasilake nilai acak, kang banjur diutus kanggo variabel b.
  • Nah, kasus karo variabel c padha karo kasus karo variabel a. Sawise iku, kabeh mandheg sedhela, nanging saiki delayAndGetRandom(1000) ora ngasilake apa-apa amarga ora dibutuhake.
  • Akibaté, nilai diwilang nggunakake rumus a + b * c. Asil wis kebungkus ing janji nggunakake Promise.resolve lan bali dening fungsi.

Ngaso iki bisa uga kaya generator ing ES6, nanging ana apa-apa alasan sampeyan.

Ngatasi masalah

Nah, saiki ayo goleki solusi kanggo masalah sing kasebut ing ndhuwur.

Fungsi finishMyTask nggunakake Await kanggo ngenteni asil operasi kayata queryDatabase, sendEmail, logTaskInFile, lan liya-liyane. Yen sampeyan mbandhingake solusi iki karo sing digunakake janji, podho bakal katon. Nanging, versi Async/Await nyederhanakake kabeh kerumitan sintaksis. Ing kasus iki, ora ana nomer akeh callbacks lan chain kaya .then/.catch.

Punika solusi karo output saka nomer, ana rong pilihan.

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

Lan ing kene ana solusi nggunakake fungsi async.

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

Kasalahan pangolahan

Kesalahan sing ora ditanggulangi dibungkus karo janji sing ditolak. Nanging, fungsi async bisa nggunakake nyoba / nyekel kanggo nangani kasalahan bebarengan.

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 () iku sawijining fungsi bedo sing salah siji sukses ("nomer sampurna") utawa gagal karo kesalahan ("Ngapunten, nomer amba banget").

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

Wiwit conto ing ndhuwur ngarepake canRejectOrReturn kanggo nglakokake, kegagalan dhewe bakal nyebabake eksekusi blok nyekel. Akibaté, fungsi foo bakal mungkasi karo salah siji undefined (nalika boten bali ing pamblokiran nyoba) utawa karo kesalahan kejiret. Akibaté, fungsi iki ora bakal gagal amarga nyoba / nyekel bakal nangani fungsi foo dhewe.

Iki conto liyane:

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

Iku worth mbayar manungsa waé kanggo kasunyatan sing ing conto, canRejectOrReturn bali saka foo. Foo ing kasus iki salah siji mungkasi karo nomer sampurna utawa ngasilake Error ("Ngapunten, nomer amba banget"). Blok nyekel ora bakal ditindakake.

Masalahe yaiku foo ngasilake janji sing diwenehake saka canRejectOrReturn. Dadi solusi kanggo foo dadi solusi kanggo canRejectOrReturn. Ing kasus iki, kode mung bakal kalebu rong baris:

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

Mangkene apa sing kedadeyan yen sampeyan nggunakake ngenteni lan bali bebarengan:

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

Ing kode ing ndhuwur, foo bakal kasil metu karo nomer sampurna lan kesalahan kejiret. Ora bakal ana penolakan ing kene. Nanging foo bakal bali karo canRejectOrReturn, ora karo undefined. Priksa manawa iki kanthi mbusak baris bali ngenteni canRejectOrReturn():

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

Kesalahan umum lan pitfalls

Ing sawetara kasus, nggunakake Async/Await bisa nyebabake kesalahan.

Kelalen ngenteni

Iki kedadeyan cukup asring - tembung kunci sing ngenteni dilalekake sadurunge janji:

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

Nalika sampeyan bisa ndeleng, ora ana ngenteni utawa bali ing kode. Mulane foo tansah metu karo undefined tanpa wektu tundha 1 detik. Nanging janji bakal kawujud. Yen mbuwang kesalahan utawa penolakan, UnhandledPromiseRejectionWarning bakal diarani.

Fungsi Async ing Callbacks

Fungsi Async asring digunakake ing .map utawa .filter minangka callback. Conto yaiku fungsi fetchPublicReposCount(jeneng panganggo), sing ngasilake jumlah repositori sing mbukak ing GitHub. Contone, ana telung pangguna sing metrik sing dibutuhake. Iki kode kanggo tugas iki:

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

Kita butuh akun ArfatSalman, octocat, norvig. Ing kasus iki kita nindakake:

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

Iku worth mbayar manungsa waé kanggo Enteni ing .map callback. Kene counts Uploaded janji, lan .map minangka callback anonim kanggo saben pangguna tartamtu.

Kebacut konsisten nggunakake ngenteni

Ayo njupuk kode iki minangka conto:

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

Ing kene nomer repo diselehake ing variabel count, banjur nomer iki ditambahake menyang array counts. Masalah karo kode kasebut yaiku nganti data pangguna pisanan teka saka server, kabeh pangguna sabanjure bakal ana ing mode siyaga. Mangkono, mung siji pangguna sing diproses sekaligus.

Yen, umpamane, butuh udakara 300 ms kanggo ngolah siji pangguna, mula kanggo kabeh pangguna wis dadi detik; wektu sing digunakake sacara linear gumantung saka jumlah pangguna. Nanging amarga entuk nomer repo ora gumantung ing siji liyane, proses bisa paralel. Iki mbutuhake nggarap .map lan 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 nampa macem-macem janji minangka input lan ngasilake janji. Sing terakhir, sawise kabeh janji ing array wis rampung utawa ing penolakan pisanan, wis rampung. Bisa uga kedadeyan yen kabeh ora diwiwiti ing wektu sing padha - kanggo mesthekake wiwitan bebarengan, sampeyan bisa nggunakake p-map.

kesimpulan

Fungsi Async dadi tambah penting kanggo pangembangan. Inggih, kanggo nggunakake adaptif fungsi async iku worth nggunakake Async Iterators. Pangembang JavaScript kudu ngerti babagan iki.

Skillbox nyaranake:

Source: www.habr.com

Add a comment