Va’dalar bilan ishlash uchun async/await deb nomlangan maxsus sintaksis mavjud. Buni tushunish va ishlatish hayratlanarli darajada oson.
Async funktsiyalari
Async kalit so’zidan boshlaymiz. Uni quyidagi funktsiyadan oldin qo’yish mumkin:
async function f() {
return 1;
}
“Async” so’zi funktsiyadan oldin bitta oddiy narsani anglatadi: funktsiya har doim va’da qaytaradi. Agar funktsiya haqiqatan ham va’da bermaydigan qiymatni qaytarsa ham, funktsiya ta’rifini “async” kalit so’zi bilan oldindan belgilash JavaScript-ni ushbu qiymatni hal qilingan va’da bilan avtomatik ravishda o’rashga yo’naltiradi.
Masalan, yuqoridagi kod 1 natijasi bilan yechilgan va’dani qaytaradi, keling uni sinab ko’raylik:
async function f() {
return 1;
}
f().then(alert); // 1
Biz aniq bir va’dani qaytara olamiz, xuddi shunday bo’lishi mumkin:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
Shunday qilib, async funktsiya va’dani qaytarishini ta’minlaydi va undagi va’dalarni yopadi. Yetarlicha sodda, to’g’rimi? Ammo bu nafaqat. Faqat async funktsiyalarida ishlaydigan yana bir await kalit so’zi bor va bu juda ajoyib.
Await
Sintaksis:
// faqat async funktsiyalar ichida ishlaydi
let value = await promise;
await kalit so’zi JavaScript-ni ushbu va’da amalga oshguncha kutib turishiga va natijasini qaytarishiga olib keladi.
1 sekund ichida hal qilinadigan va’da bilan misol:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // va'da hal bo'lguncha kuting (*)
alert(result); // "done!"
}
f();
Funktsiyaning bajarilishi (*)satrrida “to’xtaydi” va va’da bajarilgandan so’ng davom etadi, natijada result uning natijasiga aylanadi. Shunday qilib, yuqoridagi kod “done!” ko’rsatadi bir soniyada.
Keling, ta’kidlab o’tamiz: await so’zma-so’z JavaScript-ni va’da tugaguncha kutib turishi va natija bilan davom etishi kerak. Bu hech qanday CPU resurslarini talab qilmaydi, chunki interpretator boshqa ishlarni bajarishi mumkin: boshqa skriptlarni bajarish, hodisalarni boshqarish va hk.
Bu va’da natijasini olish uchun va’daga ko’ra oqlangan sintaksis promise.then , o’qish va yozish osonroq.
await dan foydalanib bo’lmaydiAgar mos kelmaydigan funktsiyada await dan foydalanishga harakat qilsak, sintaksis xatosi bo’ladi:
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}
Agar funktsiyadan oldin async qo’ymasak, bu xato bo’ladi. Yuqorida aytib o’tilganidek, await faqat async function ichida ishlaydi.
Va'dalar zanjiri bobidagi showAvatar() misolini olamiz va uni async/await yordamida qayta yozamiz:
- Biz
.thenchaqiruvlariniawaitbilan almashtirishimiz kerak. - Shuningdek, ular ishlashi uchun
asyncfunktsiyani bajarishimiz kerak.
async function showAvatar() {
// bizning JSON-ni o'qish
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// github foydalanuvchisini o'qish
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// avatarni ko'rsatish
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// 3 soniya kuting
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
Juda toza va o’qish oson, to’g’rimi? Oldingiga qaraganda ancha yaxshi.
await yuqori darajadagi kodda ishlamaydiawait dan foydalanishni yangi boshlagan odamlar, biz await dan yuqori darajadagi kodda foydalana olmasligimizni unutishadi. Masalan, bu ishlamaydi:
// yuqori darajadagi kodda sintaksis xatosi
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
Biz buni noma’lum async funktsiyasiga o’rashimiz mumkin, masalan:
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
P.S. New feature: starting from V8 engine version 8.9+, top-level await works in modules.
await “thenables” ni qabul qiladipromise.then singari, await ham ishlatilishi mumkin bo’lgan obyektlardan foydalanishga imkon beradi (then usuli bilan chaqirish mumkin). G’oya shundan iboratki, uchinchi tomon obyekti va’da bo’lmasligi mumkin, ammo va’daga mos kelishi mumkin: agar u .then ni qo’llab-quvvatlasa, await bilan ishlatish kifoya.
Demo Thenable klassi, quyida await uning holatlarini qabul qiladi:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve);
// 1000ms dan keyin this.num*2 bilan hal qilish
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
}
async function f() {
// 1 soniyani kutadi, keyin natija 2 ga teng bo'ladi
let result = await new Thenable(1);
alert(result);
}
f();
Agar await .then bilan va’da qilinmagan obyektga ega bo’lsa, u ushbu funktsiyani mahalliy funktsiyalarni resolve, reject argument sifatida chaqiradi. Keyin await ulardan bittasi chaqirilguncha kutadi (yuqoridagi misolda u (*) satrida bo’ladi) va natijada bilan davom etadi.
Async klass usulini e’lon qilish uchun uni async bilan qo’shib qo’ying:
class Waiter {
async wait() {
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1 (this is the same as (result => alert(result)))
Ma’nosi bir xil: qaytarilgan qiymat va’da bo’lishini ta’minlaydi va await ga imkon beradi.
Xato ishlov beruvchi
Agar va’da odatdagidek hal qilinsa, await promise natijani qaytaradi. Ammo rad etilgan taqdirda, u xuddi shu satrda throw iborasi bo’lganidek, xatoga yo’l qo’yadi.
Ushbu kod:
async function f() {
await Promise.reject(new Error("Whoops!"));
}
Bu xuddi shunday:
async function f() {
throw new Error("Whoops!");
}
Haqiqiy vaziyatlarda va’da rad etilishidan oldin biroz vaqt talab qilishi mumkin. Shunday qilib, await kutadi va keyin xato bo’ladi.
Ushbu xatoni odatiy throw singari try..catch yordamida topishimiz mumkin:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
Xato bo’lsa, boshqaruv catch blokiga o’tadi. Shuningdek, biz bir nechta satrlarni o’rashimiz mumkin:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// fetch va response.json-da xatolarga yo'l qo'yadi
alert(err);
}
}
f();
Agar bizda try..catch bo’lmasa, u holda f() async funktsiya chaqiruvi natijasida hosil bo’lgan va’da rad etiladi. Buni boshqarish uchun .catch qo’shib qo’yishimiz mumkin:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() rad qilingan va'daga aylanadi
f().catch(alert); // TypeError: failed to fetch // (*)
Agar u yerga .catch qo’shishni unutib qo’ysak, unda ishlov berilmagan va’da xatosi paydo bo’ladi (konsolda ko’rish mumkin). Biz Va'dalar bilan ishlashda xato bobida tasvirlangan hodisalarni global ishlov beruvchisi yordamida bunday xatolarga yo’l qo’yamiz.
async/await va promise.then/catchBiz async/await dan foydalanganda kamdan-kam hollarda .then kerak bo’ladi, chunki await bizga kutishni boshqaradi. Va .catch o’rniga odatdagi try..catch dan foydalanishimiz mumkin. Bu odatda (har doim ham) qulayroq emas.
Ammo kodning yuqori darajasida, har qanday async funktsiyasidan tashqarida bo’lganimizda, sintaktik ravishda await dan foydalana olmaymiz, shuning uchun yakuniy natijani boshqarish uchun .then/catch yoki xatolarni qo’shish odatiy holdir.
Yuqoridagi misolning (*) satridagi kabi.
async/await Promise.all bilan yaxshi ishlaydiBir nechta va’dalarni kutish kerak bo’lganda, ularni Promise.all ga o’rab, keyin await orqali kutish mumkin:
// natijalar massivini kutish
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
Xato bo’lsa, u odatdagidek tarqaladi: bajarilmagan va’dadan Promise.all gacha va keyin biz chaqiruv atrofida try..catch yordamida ushlab qolishimiz mumkin bo’lgan istisnoga aylanadi.
Mikrovazifa navbat
Mikrovazifalar va hodisalar tsikli bobida ko’rganimizdek, va’da beruvchilar asinxron tarzda bajariladi. Har bir .then/catch/finally ishlov beruvchisi avval “mikrovazifa navbatiga” kiradi va joriy kod tugagandan so’ng bajariladi.
Async/await va’dalarga asoslanadi, shuning uchun u bir xil mikrovazifalarda navbatni ichki sifatida ishlatadi va makrovazifalarga nisbatan bir xil ustuvorlikka ega.
Masalan, bizda:
setTimeout(handler, 0), nol kechikish bilanhandlerni ishga tushirishi kerak.let x = await f(), functionf()bu async, lekin darhol qaytadi.
Kodda await ning ostida setTimeout bo’lsa, qaysi biri birinchi ishlaydi?
async function f() {
return 1;
}
(async () => {
setTimeout(() => alert('timeout'), 0);
await f();
alert('await');
})();
Bu yerda noaniqlik yo’q: await har doim birinchi bo’lib tugaydi, chunki (mikrovazifa) u setTimeout ishlov berishdan yuqori ustuvorlikka ega.
Xulosa
Funktsiyadan oldin async kalit so’zi ikkita ta’sirga ega:
- Har doim va’dasini qaytaradi.
- Unda
awaitdan foydalanishga ruxsat beradi.
Va’da oldidan await kalit so’zi JavaScript-ni ushbu va’da bajarilishini kutib turishiga majbur qiladi va keyin:
- Agar bu xato bo’lsa, istisno yaratiladi, xuddi o’sha joyda
throw errorchaqirilgandek. - Aks holda, natijani qaytaradi, shuning uchun biz uni qiymatga belgilashimiz mumkin.
Ular birgalikda asinxron kod yozish uchun ajoyib asos yaratadilar, bu ham o’qilishi, ham yozilishi oson.
Async/await bilan biz kamdan-kam hollarda promise.then/catch deb yozishimiz kerak, ammo biz hali ham ularning va’dalarga asoslanganligini unutmasligimiz kerak, chunki ba’zida (masalan, tashqi tomondan) biz ushbu usullardan foydalanishimiz kerak. Shuningdek, Promise.all bir vaqtning o’zida ko’plab vazifalarni kutish uchun foydali narsa.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)