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".
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").
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.
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:
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():
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:
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:
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.