Məzmuna keç
Educora
İrəli22 dəq17 / 27

Hadisə dövrü və paralel işlər

Mühərrikin içinə bax: çağırış steki, tapşırıqlar növbəsi və mikrotapşırıqlar növbəsi. `setTimeout`, Promise və `await`-in dəqiq icra sırasını təxmin etməyi öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Çağırış stekini, tapşırıqlar və mikrotapşırıqlar növbələrini təsvir etmək
  • Sinxron kod, setTimeout, Promise və await qarışı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.

JavaScript
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
Stek: 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:

  1. 1
    Cari kodu sona qədər icra et

    Skript (və ya bir tapşırıq) stek boşalana qədər işləyir.

  2. 2
    BÜ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.

  3. 3
    Lazımdırsa, səhifəni çək

    Brauzer dəyişiklikləri ekrana çıxarır.

  4. 4
    Nö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.

JavaScript
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
Əvvəl sinxron A və F. Sonra mikrotapşırıqlar: C və E növbədə idi, D isə C bitəndə əlavə olundu. Taymer B ən sonda — ayrıca tapşırıq kimi.

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.

JavaScript
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.

JavaScript
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ə.

JavaScript
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)' ]
Ardıcıl variant ~300 ms, paralel variant ~200 ms çəkir; paralel variantda B birinci bitir, amma 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.
MetodNə vaxt bitirNəticə
Promise.allhamısı uğurlu olanda və ya ilk xətadaqiymətlər massivi və ya ilk xəta
Promise.allSettledhamısı bitəndə, heç vaxt rədd olunmur{ status, value } / { status, reason } massivi
Promise.racebirincisi bitəndə (uğur və ya xəta)ən tez gələn nəticə
Promise.anyilk uğurda; hamısı uğursuzdursa, xətailk uğurlu qiymət və ya AggregateError
JavaScript
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
Tapşırıq

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.

Tapşırıq · JavaScript
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
Tapşırıq

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.

Tapşırıq · JavaScript
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.
  • async funksiyada birinci await-ə qədər olan kod sinxron icra olunur.
  • Asılı olmayan işləri birlikdə başlat və Promise.all/allSettled ilə gözlə; race və any ilk nəticəni götürür.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4); nəyi hansı sırada çap edir?