完全理解 Promise 基本實(shí)現(xiàn)
網(wǎng)上有很多 Promise 實(shí)現(xiàn)方式留凭,看了都不是特別理解。
這里以一種更簡單的形式一步一步去理解/實(shí)現(xiàn)它杯道。這里僅涉及 Promise 構(gòu)造函數(shù)和 then 方法的實(shí)現(xiàn)
首先構(gòu)造一個(gè)最基本的 Promise 類
// version_1
class Promise {
callbacks = [];
constructor(executor) {
executor(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
_resolve(value) {
this.callbacks.forEach(callback => callback(value));
}
}
// test
new Promise(resolve => {
setTimeout(() => {
console.log('await 2s');
resolve('ok');
}, 2000);
}).then((res) => {
console.log('then', res);
})
- Promise 構(gòu)造函數(shù)會(huì)立即執(zhí)行用戶傳入的函數(shù) executor,并且把 _resolve 方法作為 executor 的參數(shù),傳給用戶處理
- 調(diào)用 then 方法(同步)赘艳,將 onFulfilled 放入callbacks隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù)克握,類似于觀察者模式蕾管。
- executor 模擬了異步,這里是過2s后執(zhí)行 resolve菩暗,對(duì)應(yīng)觸發(fā) _resolve 內(nèi)的 callbacks
.then(onFulfilled)
為何需要用一個(gè)數(shù)組存放掰曾?
then 方法可以調(diào)用多次,注冊(cè)的多個(gè)onFulfilled停团,并且這些 onFulfilled callbacks 會(huì)在異步操作完成(執(zhí)行resolve)后根據(jù)添加的順序依次執(zhí)行
// then 注冊(cè)多個(gè) onFulfilled 回調(diào)
const p = new Promise(resolve => {
setTimeout(() => {
console.log('await 2s');
resolve('ok');
}, 2000);
});
p.then(res => console.log('then1', res));
p.then(res => console.log('then2', res));
p.then(res => console.log('then3', res));
異步執(zhí)行處理 setTimeout vs status
上面 Promise 的實(shí)現(xiàn)存在一個(gè)問題:如果傳入的 executor 不是一個(gè)異步函數(shù)旷坦,resolve直接同步執(zhí)行,這時(shí) callbacks 還是空數(shù)組佑稠, 導(dǎo)致后面 then 方法注冊(cè)的 onFulfilled 回調(diào)就不會(huì)執(zhí)行(resolve 比 then 注冊(cè)先執(zhí)行)
// 同步執(zhí)行 resolve
new Promise(resolve => {
console.log('同步執(zhí)行');
resolve('同步執(zhí)行');
}).then(res => {
console.log('then', res);
})
我們知道 then 中的回調(diào)總是通過異步執(zhí)行的秒梅,我們可以在 resolve 中加入 setTimeout,將 callbacks 的執(zhí)行時(shí)機(jī)放置到JS消息隊(duì)列舌胶,這樣 then方法的 onFulfilled 會(huì)先完成注冊(cè)捆蜀,再執(zhí)行消息隊(duì)列的 resolve
// version_2
class Promise {
callbacks = [];
constructor(executor) {
executor(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
_resolve(value) {
setTimeout(() => {
this.callbacks.forEach(callback => callback(value));
})
}
}
但是這樣仍然有問題,如果我們延遲給 then 注冊(cè)回調(diào)幔嫂,這些回調(diào)也都無法執(zhí)行辆它。因?yàn)?br> 還是 resolve 先執(zhí)行完了,之后注冊(cè)的回調(diào)就無法執(zhí)行了履恩。
const p = new Promise(resolve => {
console.log('同步執(zhí)行');
resolve('同步執(zhí)行');
})
setTimeout(() => {
p.then(res => {
console.log('then', res); // never execute
})
});
可以看出 setTimeout 是無法保證 then 注冊(cè)的 onFulfilled 正確執(zhí)行的锰茉,所以這里必須加入狀態(tài)機(jī)制(pending、fulfilled似袁、rejected)洞辣,且狀態(tài)只能由 pending 轉(zhuǎn)換為解決或拒絕。
// version_3:增加狀態(tài)機(jī)制
class Promise {
callbacks = [];
status = 'pending';
value = undefined;
constructor(executor) {
executor(this._resolve.bind(this));
}
then(onFulfilled) {
if (this.status === 'pending') {
this.callbacks.push(onFulfilled);
} else {
onFulfilled(this.value);
}
}
_resolve(value) {
this.status = 'fulfilled';
this.value = value;
this.callbacks.forEach(callback => callback(value));
}
}
當(dāng)增加了狀態(tài)后昙衅,setTimeout 就可以去掉了扬霜,狀態(tài)機(jī)制讓注冊(cè)的回調(diào)總是能正確工作。
- 當(dāng) resolve 同步執(zhí)行時(shí)而涉,立即執(zhí)行 resolve著瓶,將 status 設(shè)置為 fulfilled ,并把 value 的值存起來啼县, 在此之后調(diào)用 then 添加的新回調(diào)材原,都會(huì)立即執(zhí)行
- 當(dāng) resolve 異步執(zhí)行時(shí)沸久,pending 狀態(tài)執(zhí)行 then 會(huì)添加回調(diào)函數(shù), 等到 resolve 執(zhí)行時(shí)余蟹,回調(diào)函數(shù)會(huì)全部被執(zhí)行卷胯。
then的鏈?zhǔn)秸{(diào)用
鏈?zhǔn)秸{(diào)用我們可能很直接想到 then 方法中返回 this,這樣 Promise 實(shí)例就可以多次調(diào)用 then 方法威酒,但因?yàn)槭峭粋€(gè)實(shí)例窑睁,調(diào)用再多次 then 也只能返回相同的一個(gè)結(jié)果。而我們希望的鏈?zhǔn)秸{(diào)用應(yīng)該是這樣的:
new Promise(resolve => {
resolve(1)
}).then(res => res + 2) // 1 + 2 = 3
.then(res => res + 3) // 3 + 3 = 6
.then(res => console.log(res)); // expected 6
每個(gè) then 注冊(cè)的 onFulfilled 都返回不同結(jié)果葵孤,并把結(jié)果傳給下一個(gè) onFulfilled 的參數(shù)担钮,所以 then 需要返回一個(gè)新的 Promise 實(shí)例
// version_4:then 的鏈?zhǔn)秸{(diào)用
class Promise {
callbacks = [];
status = 'pending';
value = undefined;
constructor(executor) {
executor(this._resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolveNext => {
const fulfilled = (value) => {
const results = onFulfilled(value); // 執(zhí)行 onFulfilled
resolveNext(results); // 再執(zhí)行 resolveNext
}
if (this.status === 'pending') {
this.callbacks.push(fulfilled);
} else {
fulfilled(this.value);
}
})
}
_resolve(value) {
this.status = 'fulfilled';
this.value = value;
this.callbacks.forEach(callback => callback(value));
}
}
這樣一個(gè) Promise 就基本實(shí)現(xiàn)了,我們可以看到:
- then 方法中尤仍,創(chuàng)建并返回了新的 Promise 實(shí)例箫津,這是串行 Promise 的基礎(chǔ)
- 我們把 then 方法傳入的 形參 onFulfilled 以及創(chuàng)建新 Promise 實(shí)例時(shí)傳入的 resolveNext 合成一個(gè) 新函數(shù) fulfilled,這是銜接當(dāng)前 Promise 和后鄰 Promise 的關(guān)鍵所在
處理返回 Promise 類型的回調(diào)
這里還有一種特殊的情況:
- resolve 方法傳入的參數(shù)為一個(gè) Promise 對(duì)象時(shí)
- onFulfilled 方法返回一個(gè) Promise 對(duì)象時(shí)
這時(shí)我們只需用 res instanceof Promise
判斷處理下
// version_5:Promise 參數(shù)處理
class Promise {
callbacks = [];
status = 'pending';
value = undefined;
constructor(executor) {
executor(this._resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolveNext => {
const fulfilled = (value) => {
const results = onFulfilled(value);
if (results instanceof Promise) {
// 如果當(dāng)前回調(diào)函數(shù)返回Promise對(duì)象宰啦,必須等待其狀態(tài)改變后在執(zhí)行下一個(gè)回調(diào)
results.then(resolveNext);
} else {
// 否則會(huì)將返回結(jié)果直接作為參數(shù)苏遥,傳入下一個(gè)then的回調(diào)函數(shù),并立即執(zhí)行下一個(gè)then的回調(diào)函數(shù)
resolveNext(results);
}
}
if (this.status === 'pending') {
this.callbacks.push(fulfilled);
} else {
fulfilled(this.value);
}
})
}
_resolve(value) {
this.status = 'fulfilled';
/**
* 如果resolve的參數(shù)為Promise對(duì)象绑莺,則必須等待該P(yáng)romise對(duì)象狀態(tài)改變后,
* 當(dāng)前Promsie的狀態(tài)才會(huì)改變暖眼,且狀態(tài)取決于參數(shù)Promsie對(duì)象的狀態(tài)
*/
if (value instanceof Promise) {
value.then(nextValue => {
this.value = nextValue;
this.callbacks.forEach(callback => callback(value));
})
} else {
this.value = value;
this.callbacks.forEach(callback => callback(value));
}
}
}
拓展練習(xí)
嘗試實(shí)現(xiàn)下面函數(shù) LazyMan
的功能
LazyMan('Jack').sleep(3).eat('apple');
// Hi! Jack
// await 3s
// Jack eat apple~