Promise ichki mexanizmi
Promise'ning uchta holati ([[PromiseState]]), internal slotlari, executor'ning sinxron ishlashi, resolve/reject closure orqali qanday bog'langani, .then() yangi Promise qaytarishi, Resolution Procedure orqali flatten bo'lishi, .catch() propagation, va .then() nega har doim microtask ekani.
48-dars
Promise ichki mexanizmi
Promise — bu kelajakda tayyor bo'ladigan qiymatning "o'rinbosari" (placeholder). Callback'dan farqi: Promise natijani "funksiya orqali yetkazish" o'rniga, natijani obyekt ichida saqlaydi, va bu obyektning holati o'zgarganda, unga "obuna bo'lgan" funksiyalar avtomatik chaqiriladi.
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('natija tayyor'), 1000);
});
promise.then((result) => console.log(result)); // 1 sekunddan keyin: "natija tayyor"
1. Uchta holat (state) — Promise'ning yuragi
Har bir Promise obyekti ichida [[PromiseState]] degan yashirin (internal) maydon bor, va u faqat uchta qiymatdan birini oladi:
pending → hali natija yo'q, kutilyapti (boshlang'ich holat)
fulfilled → muvaffaqiyatli tugadi, natija bor
rejected → xato bilan tugadi, sabab bor
Muhim qoida — bir marta o'zgaradi, keyin qotib qoladi:
pending → fulfilled (qaytib bo'lmaydi)
pending → rejected (qaytib bo'lmaydi)
new Promise((resolve, reject) => {
resolve('birinchi');
resolve('ikkinchi'); // hech narsa qilmaydi — allaqachon fulfilled
reject('xato'); // bu ham e'tiborsiz qoladi
});
Bu — callback'dagi "ikki marta chaqirilishi mumkin" muammosini til darajasida hal qiladi.
2. Promise obyektining ichki tuzilishi (V8/spec darajasida)
new Promise((resolve, reject) => {...}) chaqirilganda, V8 xotirada quyidagi internal slotlarga ega obyekt yaratadi (bular JS kodidan ko'rinmaydi, faqat DevTools orqali qisman ko'rinadi):
Promise obyekti (heap'da):
┌────────────────────────────────────────┐
│ [[PromiseState]]: "pending" │ ← holat
│ [[PromiseResult]]: undefined │ ← natija/sabab (hozircha bo'sh)
│ [[PromiseFulfillReactions]]: [] │ ← .then() bilan qo'shilgan "muvaffaqiyat" callback'lar ro'yxati
│ [[PromiseRejectReactions]]: [] │ ← .then()/.catch() bilan qo'shilgan "xato" callback'lar ro'yxati
│ [[PromiseIsHandled]]: false │ ← unhandled rejection uchun belgi
└────────────────────────────────────────┘
Executor funksiya — (resolve, reject) => {...} — darhol, sinxron chaqiriladi (bu ko'p odam bilmaydigan muhim nuqta):
console.log('1');
new Promise((resolve) => {
console.log('2'); // BU DARHOL, SINXRON chiqadi!
resolve();
});
console.log('3');
// natija: 1, 2, 3 — Promise konstruktorining o'zi async EMAS
Faqat .then() ichidagi callback — asinxron (microtask). Konstruktor ichidagi kod — oddiy sinxron kod.
3. resolve va reject — bular aslida nima?
resolve va reject — V8 tomonidan maxsus yaratilgan ikkita funksiya, ular closure orqali shu aniq Promise obyektiga bog'langan. Soddalashtirilgan tasavvur:
function createResolve(promiseInternal) {
return function resolve(value) {
if (promiseInternal.state !== 'pending') return; // qotib qolgan, chiqib ket
promiseInternal.state = 'fulfilled';
promiseInternal.result = value;
// navbatdagi barcha .then() callback'larni MICROTASK sifatida rejalashtir
scheduleReactions(promiseInternal.fulfillReactions, value);
};
}
resolve(value) chaqirilganda, u darhol callback'larni chaqirmaydi — u faqat holatni o'zgartiradi va reaksiyalarni (agar bor bo'lsa) Microtask Queue'ga rejalashtiradi.
4. .then() — qadam-baqadam, ichkarida nima bo'ladi
Bu — eng muhim qism. .then(onFulfilled, onRejected) chaqirilganda uchta narsa sodir bo'ladi:
.then()— DARHOL yangi Promise qaytaradi (holati hali qanday bo'lishidan qat'i nazar):
const p1 = new Promise((resolve) => resolve(10));
const p2 = p1.then((val) => val * 2); // p2 — YANGI, alohida Promise
-
Agar
p1hali pending bo'lsa —onFulfilledfunksiyap1ning[[PromiseFulfillReactions]]ro'yxatiga qo'shib qo'yiladi (hozircha chaqirilmaydi, faqat "ro'yxatga yozib qo'yiladi"). -
Agar
p1allaqachon fulfilled bo'lsa (yokiresolvechaqirilgan payt) — reaksiya darhol Microtask Queue'ga joylashtiriladi (lekin baribir sinxron emas — bajarilishi Call Stack bo'shashini kutadi).
Diagram bilan ko'rsataman:
const p1 = new Promise((resolve) => {
setTimeout(() => resolve(5), 1000);
});
p1.then((val) => console.log(val * 2)); // (A)
p1.then((val) => console.log(val * 3)); // (B)
p1 [[PromiseFulfillReactions]]: [ ]
│
▼ .then() chaqirilganda (A)
p1 [[PromiseFulfillReactions]]: [ reactionA ]
│
▼ .then() chaqirilganda (B)
p1 [[PromiseFulfillReactions]]: [ reactionA, reactionB ]
│
▼ 1000ms o'tib, resolve(5) chaqiriladi
p1 [[PromiseState]] = "fulfilled", [[PromiseResult]] = 5
│
▼ ro'yxatdagi HAR BIR reaksiya Microtask Queue'ga qo'shiladi
Microtask Queue: [ reactionA(5), reactionB(5) ]
│
▼ Event Loop, Call Stack bo'shagach, navbat bilan bajaradi
console.log(10) console.log(15)
Shuning uchun bitta Promise'ga bir nechta .then() ulash mumkin — ularning barchasi bitta ro'yxatda saqlanadi va Promise fulfilled/rejected bo'lganda hammasi birdek ishga tushiriladi.
5. Nega .then() natijasi — YANGI Promise (Chaining sirlarining kaliti)
Promise.resolve(1)
.then((val) => val + 1) // p2
.then((val) => val + 1) // p3
.then((val) => console.log(val)); // 3
Har bir .then() — yangi Promise yaratadi, va bu yangi Promise'ning holati onFulfilled funksiyaning qaytargan qiymatiga bog'liq:
onFulfilled MUMKIN QAYTARSIN:
- oddiy qiymat → yangi Promise darhol shu qiymat bilan "fulfilled" bo'ladi
- Promise → yangi Promise O'SHA Promise HAL BO'LGUNCHA "pending" qoladi,
keyin uning holatini "meros qilib oladi" (bu — Resolution Procedure)
- xato tashlasa (throw) → yangi Promise "rejected" bo'ladi
Ikkinchi holat — eng muhim va murakkab qism, chunki aynan shu orqali ichma-ich Promise'lar avtomatik "yozilib" (flatten) ketadi:
function getUser() {
return new Promise((resolve) => setTimeout(() => resolve({ id: 1 }), 1000));
}
function getOrders(user) {
return new Promise((resolve) => setTimeout(() => resolve(['order1']), 1000));
}
getUser()
.then((user) => getOrders(user)) // BU YERDA Promise qaytarilyapti!
.then((orders) => console.log(orders));
Ikkinchi .then() — ordersni to'g'ridan-to'g'ri massiv sifatida oladi, Promise<Promise<array>> emas. Bu — Promise Resolution Procedure (spec'da [[Resolve]](value) deb ataladi) tufayli:
onFulfilledqaytargan qiymat — Promise-mi (yoki "thenable", ya'ni.thenmetodiga ega har qanday obyekt)-mi tekshiriladi- Agar HA bo'lsa:
- yangi Promise (
p_next) holati "pending"da QOLDIRILADI - ichki qaytarilgan Promise'ning O'ZIGA
.then()ulanadi (ichki, avtomatik) - ichki Promise hal bo'lganda, UNING natijasi
p_next'ga "uzatiladi" (ya'nip_nextENDI resolve/reject qilinadi)
- yangi Promise (
- Agar YO'Q bo'lsa (oddiy qiymat):
p_nextdarhol shu qiymat bilan fulfilled qilinadi
Shu mexanizm tufayli, nechta Promise ichma-ich qaytarilishidan qat'i nazar, zanjir hech qachon Promise<Promise<Promise<...>>> shaklida "yig'ilib" qolmaydi — har doim "tekislanadi" (flatten).
6. .catch() va xato tarqalishi (propagation)
.catch(fn) — aslida shunchaki .then(undefined, fn) uchun qisqartma (syntactic sugar). Xato zanjir bo'ylab keyingi .catch() yoki ikkinchi argumentli .then() topilguncha "sakrab" o'tadi:
Promise.reject('xato')
.then((val) => console.log('bu ishlamaydi')) // o'tkazib yuboriladi (onRejected yo'q)
.then((val) => console.log("bu ham o'tkaziladi")) // yana o'tkaziladi
.catch((err) => console.log('ushlandi:', err)); // faqat SHU YERDA ushlanadi
Ichkarida bu qanday ishlaydi: har bir .then() — agar onRejected argumenti berilmagan bo'lsa, V8 default reaksiya qo'yadi — bu reaksiya shunchaki qabul qilingan xatoni qayta tashlaydi (re-throw), ya'ni keyingi Promise'ni ham rejected qilib, sabab bir xil qoladi. Shu tariqa xato .catch() topilguncha zanjir bo'ylab "oqib" boradi.
7. Nega .then() ichidagi kod — MICROTASK (V8'dagi asosiy sabab)
resolve(value) chaqirilganda, reaksiyalar Call Stack'ga darhol qo'yilmaydi — chunki bu ECMAScript spesifikatsiyasida qat'iy talab qilingan: Promise reaksiyalari har doim PromiseJobs (V8'da bu Microtask deb amalga oshirilgan) sifatida ishga tushishi kerak, hech qachon sinxron emas. Sabab — izchillik:
function test(promise) {
promise.then(() => console.log('ichida'));
console.log('tashqarida');
}
test(Promise.resolve()); // agar allaqachon resolved bo'lsa ham...
Agar resolve() allaqachon chaqirilgan bo'lsa ham, .then()ning callback'i hech qachon darhol chaqirilmaydi — u har doim, hatto Promise darhol tayyor bo'lsa ham, navbatdagi microtask sifatida ishlaydi. Shu tufayli natija har doim: tashqarida, keyin ichida — kod qachon .then() chaqirilganidan qat'i nazar, tartib bashorat qilinadigan (predictable) bo'lib qoladi. Aks holda, ba'zida sinxron, ba'zida asinxron ishlaydigan funksiya — dasturchi uchun eng xavfli holatlardan biri bo'lardi ("Zalgo" muammosi deb ataladi).
Xulosa diagram — hammasi birga
new Promise(executor)
│
▼ executor DARHOL, sinxron chaqiriladi
│
resolve(v) yoki reject(e) chaqirilishini kutamiz
│
▼
[[PromiseState]]: pending → fulfilled/rejected (BIR MARTA, qaytmas)
│
▼
Ro'yxatdagi barcha .then()/.catch() reaksiyalari
│
▼
Har biri MICROTASK QUEUE'ga qo'yiladi (V8 ichida, spec darajasida)
│
▼
Event Loop, Call Stack bo'shagach, navbat bilan bajaradi
│
▼
Har bir .then() — YANGI Promise qaytaradi → agar qaytgan qiymat Promise bo'lsa,
Resolution Procedure orqali "tekislanadi" (flatten)