- Çağırış stekini, tapşırıqlar və mikrotapşırıqlar növbələrini təsvir etmək
- Sinxron kod,
setTimeout, Promise vəawaitqarışıq olan kodun çıxış sırasını təxmin etmək - Ardıcıl və paralel asinxron kod arasında seçim etmək:
Promise.all,allSettled,race,any
Müsahibələrin klassik sualı: «Bu kod nəyi hansı sırada çap edəcək?» — və qarşında console.log, setTimeout(..., 0) və Promise.then qarışığı. Təcrübəli proqramçılar da tez-tez səhv edir. JavaScript tək axında işləyir, amma minlərlə kliki, sorğunu və taymeri idarə edir. Sirr hadisə dövründədir (event loop). Üç quruluşu — çağırış stekini, tapşırıqlar növbəsini və mikrotapşırıqlar növbəsini — başa düşən kimi belə tapmacalar sadə hesaba çevrilir.
Çağırış steki
Çağırış steki hazırda icra olunan funksiyaların siyahısıdır. Funksiya çağırılanda stekin üstünə yeni «çərçivə» qoyulur, funksiya qayıdanda götürülür. JavaScript-in bir steki var, yəni o, eyni anda yalnız bir iş görür. Stek məşğul olduqca başqa heç nə baş verə bilməz: nə klik emal olunur, nə taymer işləyir, nə də səhifə yenidən çəkilir.
function third() {
console.log('3. third is on top of the stack');
}
function second() {
console.log('2. second calls third');
third();
console.log('4. back in second');
}
function first() {
console.log('1. first calls second');
second();
console.log('5. back in first');
}
first();▸ Gözlənilən nəticə
1. first calls second 2. second calls third 3. third is on top of the stack 4. back in second 5. back in first
first → second → third. Hər funksiya ancaq içindəki çağırış bitəndən sonra davam edir. Sonsuz rekursiya steki doldurur və mühərrik xəta atır.Tapşırıqlar və mikrotapşırıqlar
Asinxron iş (taymer, şəbəkə cavabı, klik) bitəndə onun callback-i dərhal icra olunmur — növbədə gözləyir. İki əsas növbə var. Tapşırıqlar növbəsinə (macrotask) setTimeout, setInterval, istifadəçi hadisələri və mesajlar düşür. Mikrotapşırıqlar növbəsinə isə Promise callback-ləri (then, catch, finally), await-dən sonrakı kod və queueMicrotask düşür. Hadisə dövrü bu növbələri belə işlədir:
- 1Cari kodu sona qədər icra et
Skript (və ya bir tapşırıq) stek boşalana qədər işləyir.
- 2BÜTÜN mikrotapşırıqları icra et
Bu vaxt yaranan yeni mikrotapşırıqlar da daxil olmaqla — növbə tam boşalana qədər.
- 3Lazımdırsa, səhifəni çək
Brauzer dəyişiklikləri ekrana çıxarır.
- 4Növbədən BİR tapşırıq götür
Məsələn, bir taymer callback-i — və yenidən birinci addıma qayıt.
console.log('A: script start');
setTimeout(() => console.log('B: timeout'), 0);
Promise.resolve()
.then(() => console.log('C: promise 1'))
.then(() => console.log('D: promise 2'));
queueMicrotask(() => console.log('E: microtask'));
console.log('F: script end');▸ Gözlənilən nəticə
A: script start F: script end C: promise 1 E: microtask D: promise 2 B: timeout
async/await növbədə
await yalnız öz async funksiyasını dayandırır; await-dən sonrakı kod mikrotapşırıq kimi davam edir. Diqqət: birinci await-ə qədər olan kod sinxron icra olunur — funksiya çağırılan kimi.
async function load() {
console.log('2. load starts: synchronous part');
await null;
console.log('4. after await: microtask');
}
console.log('1. script start');
setTimeout(() => console.log('6. timeout: task'), 0);
load();
Promise.resolve().then(() => console.log('5. then: microtask'));
console.log('3. script end');▸ Gözlənilən nəticə
1. script start 2. load starts: synchronous part 3. script end 4. after await: microtask 5. then: microtask 6. timeout: task
Mikrotapşırıqlar növbəsi növbəti tapşırıqdan əvvəl tam boşaldılır. Ona görə taymerin içində yaranan Promise növbəti taymerdən əvvəl icra olunur. Eyni səbəbdən özünü sonsuz təkrarlayan mikrotapşırıq səhifəni sonsuz while dövrü kimi dondurar.
setTimeout(() => {
console.log('timeout 1');
Promise.resolve().then(() => console.log('-> microtask inside timeout 1'));
}, 0);
setTimeout(() => console.log('timeout 2'), 0);
Promise.resolve().then(() => {
console.log('microtask 1');
setTimeout(() => console.log('timeout 3, added by microtask 1'), 0);
});▸ Gözlənilən nəticə
microtask 1 timeout 1 -> microtask inside timeout 1 timeout 2 timeout 3, added by microtask 1
Ardıcıl və paralel
JavaScript tək axınlıdır, amma gözləmələr üst-üstə düşə bilər: şəbəkə sorğularını və taymerləri arxa planda brauzer (və ya Node.js) izləyir. Buna konkurentlik deyilir: eyni anda çox iş «gedir», amma hər an yalnız bir JavaScript parçası icra olunur. Tez-tez edilən səhv: dövrün içində await sorğuları bir-birinin ardınca göndərir. İşlər asılı deyilsə, hamısını birlikdə başlat və Promise.all ilə gözlə.
const task = (label, ms) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(`${label} done`);
resolve(label);
}, ms);
});
console.log('Sequential:');
await task('A (200 ms)', 200);
await task('B (100 ms)', 100);
console.log('Parallel:');
const results = await Promise.all([task('A (200 ms)', 200), task('B (100 ms)', 100)]);
console.log(results);▸ Gözlənilən nəticə
Sequential: A (200 ms) done B (100 ms) done Parallel: B (100 ms) done A (200 ms) done [ 'A (200 ms)', 'B (100 ms)' ]
Promise.all nəticələri massivdəki sıra ilə qaytarır. Ən üst səviyyədəki await burada modullarda olduğu kimi işləyir.| Metod | Nə vaxt bitir | Nəticə |
|---|---|---|
Promise.all | hamısı uğurlu olanda və ya ilk xətada | qiymətlər massivi və ya ilk xəta |
Promise.allSettled | hamısı bitəndə, heç vaxt rədd olunmur | { status, value } / { status, reason } massivi |
Promise.race | birincisi bitəndə (uğur və ya xəta) | ən tez gələn nəticə |
Promise.any | ilk uğurda; hamısı uğursuzdursa, xəta | ilk uğurlu qiymət və ya AggregateError |
const ok = (value, ms) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
const fail = (reason, ms) =>
new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
const settled = await Promise.allSettled([ok('menu', 100), fail('no stock', 50)]);
console.log(settled.map((result) => result.status));
console.log(await Promise.any([fail('server 1 down', 50), ok('server 2', 100)]));
console.log(await Promise.race([ok('fast', 50), ok('slow', 150)]));
try {
await Promise.all([ok('menu', 100), fail('no stock', 50)]);
} catch (error) {
console.log('Promise.all failed:', error.message);
}▸ Gözlənilən nəticə
[ 'fulfilled', 'rejected' ] server 2 fast Promise.all failed: no stock
Sətirlərin yerini dəyişmədən çıxışı 1, 2, 3, 4 sırasına sal: lazım olan sətirləri setTimeout(() => ..., 0) və ya Promise.resolve().then(() => ...) ilə sar.
console.log('4. task');
console.log('3. microtask');
console.log('1. sync');
console.log('2. sync');▸ Gözlənilən nəticə
1. sync 2. sync 3. microtask 4. task
Qiymətlər ardıcıl yüklənir və bu, yavaşdır. Kodu elə dəyiş ki, üç sorğu eyni anda başlasın: Promise.all və massiv destrukturlaşdırmasından istifadə et. Yükləmə sırası dəyişəcək, cəm isə eyni qalacaq.
const prices = { tea: 2, bread: 1, jam: 5 };
function getPrice(item, ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`loaded ${item}`);
resolve(prices[item]);
}, ms);
});
}
// sequential and slow: rewrite with Promise.all
const tea = await getPrice('tea', 300);
const bread = await getPrice('bread', 100);
const jam = await getPrice('jam', 200);
console.log('Total:', tea + bread + jam);▸ Gözlənilən nəticə
loaded bread loaded jam loaded tea Total: 8
Əsas fikirlər
- JavaScript-in bir çağırış steki var: eyni anda yalnız bir kod parçası işləyir, uzun sinxron iş hər şeyi dondurur.
- Taymerlər və hadisələr tapşırıqlar növbəsinə, Promise callback-ləri və
await-dən sonrakı kod mikrotapşırıqlar növbəsinə düşür. - Sıra: sinxron kod → bütün mikrotapşırıqlar → bir tapşırıq → yenə bütün mikrotapşırıqlar.
asyncfunksiyada birinciawait-ə qədər olan kod sinxron icra olunur.- Asılı olmayan işləri birlikdə başlat və
Promise.all/allSettledilə gözlə;racevəanyilk nəticəni götürür.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4); nəyi hansı sırada çap edir?