Məzmuna keç
Educora
İrəli20 dəq12 / 27

Asinxron JavaScript: callback, Promise, async/await

Gözləmə tələb edən işləri proqramı dondurmadan yerinə yetirməyi öyrən: `setTimeout`, callback-lər, Promise və `async`/`await`.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Sinxron və asinxron kodun fərqini və setTimeout-un işini izah etmək
  • Promise yaratmaq və then, catch, finally ilə nəticəni emal etmək
  • async/await və try...catch ilə oxunaqlı asinxron kod yazmaq
  • Promise.all ilə bir neçə işi paralel icra etmək

Kafedə ofisiant sifarişini mətbəxə verir, amma plov bişənə qədər mətbəxin qapısında dayanıb gözləmir — o, başqa masalara xidmət edir. Yemək hazır olanda isə onu gətirir. JavaScript də eyni cür işləyir: serverdən cavab, taymer, fayl kimi uzun çəkən işləri gözləyərkən səhifəni dondurmur, başqa işlərlə məşğul olur. Buna asinxron proqramlaşdırma deyilir.

Sinxron və asinxron

İndiyə qədər yazdığımız kod sinxron idi: hər əmr əvvəlkinin bitməsini gözləyir. setTimeout(funksiya, ms) isə funksiyanı göstərilən millisaniyədən sonra icra olunmaq üçün «sonraya saxlayır», proqram isə dərhal növbəti sətrə keçir. Nəticələrin sırasına diqqət et:

JavaScript
console.log('1. Order placed');

setTimeout(() => {
  console.log('4. Plov is ready');
}, 1000);

setTimeout(() => {
  console.log('3. Tea is ready');
}, 0);

console.log('2. Waiter serves another table');
▸ Gözlənilən nəticə
1. Order placed
2. Waiter serves another table
3. Tea is ready
4. Plov is ready
Tərif
Hadisə dövrü (event loop)

JavaScript eyni anda yalnız bir iş görür. Hazır olan callback-lər növbəyə düşür, hadisə dövrü isə cari sinxron kod bitəndə onları bir-bir növbədən götürüb icra edir. Ona görə 0 ms gecikməli setTimeout da bütün sinxron koddan sonra işləyir.

Callback-lər və onların problemi

Asinxron iş bitəndə nəticəni almağın ən köhnə üsulu callback ötürməkdir: «Hazır olanda bu funksiyanı çağır». Bir neçə iş ardıcıl getməlidirsə, callback-lər bir-birinin içinə girir və kod sağa doğru «pilləkən» kimi uzanır. Proqramçılar buna callback hell deyirlər.

JavaScript
function cook(dish, callback) {
  setTimeout(() => callback(`${dish} is ready`), 300);
}

cook('Plov', (first) => {
  console.log(first);
  cook('Dolma', (second) => {
    console.log(second);
    cook('Pakhlava', (third) => {
      console.log(third);
    });
  });
});
▸ Gözlənilən nəticə
Plov is ready
Dolma is ready
Pakhlava is ready

Promise

Promise (vəd) gələcəkdə hazır olacaq nəticəni təmsil edən obyektdir. Onun üç vəziyyəti var: pending (gözləmədə), fulfilled (uğurla yerinə yetirilib) və rejected (xəta ilə bitib). Promise yaradanda iki funksiya alırıq: uğur üçün resolve, xəta üçün reject. Nəticəni then ilə, xətanı catch ilə alırıq; finally isə hər halda işləyir.

JavaScript
function cook(dish, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!dish) {
        reject(new Error('No dish ordered'));
      } else {
        resolve(`${dish} is ready`);
      }
    }, time);
  });
}

cook('Plov', 300)
  .then((message) => {
    console.log(message);
    return cook('Dolma', 200);
  })
  .then((message) => console.log(message))
  .then(() => cook('', 100))
  .catch((error) => console.log('Error:', error.message))
  .finally(() => console.log('Kitchen closed'));
▸ Gözlənilən nəticə
Plov is ready
Dolma is ready
Error: No dish ordered
Kitchen closed
Pilləkən yox oldu: hər then növbəti addımı qaytarır, bütün xətalar isə bir catch-də tutulur.

async və await

async/await Promise-lərlə işi adi, sinxron kod kimi yazmağa imkan verir. async sözü ilə elan olunan funksiya həmişə Promise qaytarır. Onun içində await Promise-in nəticəsini gözləyir, amma proqramın qalan hissəsini dondurmur. Xətaları isə tanış try...catch ilə tuturuq.

JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function cook(dish, time) {
  await wait(time);
  if (!dish) throw new Error('No dish ordered');
  return `${dish} is ready`;
}

async function dinner() {
  try {
    console.log(await cook('Plov', 300));
    console.log(await cook('Dolma', 200));
    console.log(await cook('', 100));
  } catch (error) {
    console.log('Error:', error.message);
  } finally {
    console.log('Kitchen closed');
  }
}

dinner();
▸ Gözlənilən nəticə
Plov is ready
Dolma is ready
Error: No dish ordered
Kitchen closed
Nəticə Promise zənciri ilə eynidir, amma kodu oxumaq xeyli asandır.
JavaScript
async function getScore() {
  return 42;
}

const result = getScore();
console.log(result instanceof Promise);

result.then((score) => console.log('Score:', score));
▸ Gözlənilən nəticə
true
Score: 42

Paralel işlər və fetch

İşlər bir-birindən asılı deyilsə, onları növbə ilə gözləmək vaxt itkisidir. Promise.all bütün Promise-ləri eyni anda başladır və hamısı hazır olanda nəticələri eyni sırada massiv kimi qaytarır. Aşağıdakı üç iş ardıcıl 600 ms çəkərdi, paralel isə təxminən 300 ms çəkir. Bircə Promise uğursuz olsa, Promise.all da xəta ilə bitir.

JavaScript
const cook = (dish, ms) =>
  new Promise((resolve) => setTimeout(() => resolve(`${dish} is ready`), ms));

async function breakfast() {
  const results = await Promise.all([
    cook('Tea', 300),
    cook('Bread', 100),
    cook('Jam', 200),
  ]);
  console.log(results);
}

breakfast();
▸ Gözlənilən nəticə
[ 'Tea is ready', 'Bread is ready', 'Jam is ready' ]

Real layihələrdə ən çox rast gələcəyin asinxron əməliyyat serverdən verilənləri fetch ilə yükləməkdir. fetch Promise qaytarır; cavab gələndə response.json() onu JavaScript obyektinə çevirir — bu da Promise-dir. Aşağıdakı nümunə sxemdir: ünvan şərti olaraq götürülüb.

JavaScript
async function loadWeather(city) {
  try {
    const response = await fetch(`https://api.example.com/weather?city=${city}`);
    if (!response.ok) {
      throw new Error(`Server answered ${response.status}`);
    }
    const data = await response.json();
    console.log(`${city}: ${data.temperature} °C`);
  } catch (error) {
    console.log('Could not load the weather:', error.message);
  }
}

loadWeather('Baku');
ÜsulÜstünlüyüÇatışmazlığı
Callbacksadədir, hər yerdə işləyiriç-içə girəndə oxunmur
Promisezəncir, bir catch bütün xətalar üçünuzun zəncirlər yenə çətin oxunur
async/awaitadi kod kimi oxunur, try...catchawait-i unutmaq asandır
Tapşırıq

countdown(from) asinxron funksiyasını yaz: o, from-dan 1-ə qədər ədədləri çap etsin, hər ədəddən sonra wait(300) ilə 300 ms gözləsin, sonda isə Go! çap etsin. Son sətirdəki await burada modulun ən üst səviyyəsində olduğu kimi işləyir.

Tapşırıq · JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function countdown(from) {
  // print from, from - 1, ..., 1 with a 300 ms pause after each number
  // then print Go!
}

await countdown(3);
▸ Gözlənilən nəticə
3
2
1
Go!

Əsas fikirlər

  • Asinxron kod gözləmə zamanı proqramı dondurmur; setTimeout funksiyanı sonraya saxlayır.
  • Hazır callback-lər yalnız bütün sinxron kod bitəndən sonra icra olunur (hadisə dövrü).
  • Promise-in üç vəziyyəti var: pending, fulfilled, rejected; nəticə then, xəta catch ilə alınır.
  • async funksiya həmişə Promise qaytarır; await nəticəni gözləyir, xətalar try...catch ilə tutulur.
  • Promise.all asılı olmayan işləri paralel icra edir və nəticələri eyni sırada qaytarır.

Özünü yoxla

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

1 / 10
console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C'); hansı sırada çap edəcək?