模擬實現 Promise(小白版)
模擬實現 Promise(小白版)
本篇來講講如何模擬實現一個 Promise 的基本功能,網上這類文章已經很多,本篇筆墨會比較多,因為想用自己的理解,用白話文來講講
Promise 的基本規範,參考了這篇:【翻譯】Promises/A+規範
但說實話,太多的專業術語,以及基本按照標準規範格式翻譯而來,有些內容,如果不是對規範的閱讀方式比較熟悉的話,那是很難理解這句話的內容的
我就是屬於沒直接閱讀過官方規範的,所以即使在看中文譯版時,有些表達仍舊需要花費很多時間去理解,基於此,才想要寫這篇
Promise 基本介紹
Promise 是一種非同步程式設計方案,通過 then 方法來註冊回撥函式,通過建構函式引數來控制非同步狀態
Promise 的狀態變化有兩種,成功或失敗,狀態一旦變更結束,就不會再改變,後續所有註冊的回撥都能接收此狀態,同時非同步執行結果會通過引數傳遞給回撥函式
使用示例
var p = new Promise((resolve, reject) => { // do something async job // resolve(data); // 任務結束,觸發狀態變化,通知成功回撥的處理,並傳遞結果資料 // reject(err); // 任務異常,觸發狀態變化,通知失敗回撥的處理,並傳遞失敗原因 }).then(value => console.log(value)) .catch(err => console.error(err)); p.then(v => console.log(v), err => console.error(err));
上述例子是基本用法,then 方法返回一個新的 Promise,所以支援鏈式呼叫,可用於一個任務依賴於上一個任務的執行結果這種場景
對於同一個 Promise 也可以呼叫多次 then 來註冊多個回撥處理
通過使用來理解它的功能,清楚它都支援哪些功能後,我們在模擬實現時,才能知道到底需要寫些什麼程式碼
所以,這裡來比較細節的羅列下 Promise 的基本功能:
- Promise 有三種狀態:Pending(執行中)、Resolved(成功)、Rejected(失敗),狀態一旦變更結束就不再改變
- Promise 建構函式接收一個函式引數,可以把它叫做 task 處理函式
- task 處理函式用來處理非同步工作,這個函式有兩個引數,也都是函式型別,當非同步工作結束,就是通過呼叫這兩個函式引數來通知 Promise 狀態變更、回撥觸發、結果傳遞
- Promise 有一個 then 方法用於註冊回撥處理,當狀態變化結束,註冊的回撥一定會被處理,即使是在狀態變化結束後才通過 then 註冊
- then 方法支援呼叫多次來註冊多個回撥處理
- then 方法接收兩個可選引數,這兩個引數型別都是函式,也就是需要註冊的回撥處理函式,分別是成功時的回撥函式,失敗時的回撥函式
- 這些回撥函式有一個引數,型別任意,值就是任務結束需要通知給回撥的結果,通過呼叫 task 處理函式的引數(型別是函式)傳遞過來
- then 方法返回一個新的 Promise,以便支援鏈式呼叫,新 Promise 狀態的變化依賴於回撥函式的返回值,不同型別處理方式不同
- then 方法的鏈式呼叫中,如果中間某個 then 傳入的回撥處理不能友好的處理回撥工作(比如傳遞給 then 非函式型別引數),那麼這個工作會繼續往下傳遞給下個 then 註冊的回撥函式
- Promise 有一個 catch 方法,用於註冊失敗的回撥處理,其實是
then(null, onRejected)
的語法糖 - task 處理函式或者回調函式執行過程發生程式碼異常時,Promise 內部自動捕獲,狀態直接當做失敗來處理
new Promise(task)
時,傳入的 task 函式就會馬上被執行了,但傳給 then 的回撥函式,會作為微任務放入佇列中等待執行(通俗理解,就是降低優先順序,延遲執行,不知道怎麼模擬微任務的話,可以使用 setTimeout 生成的巨集任務來模擬)
這些基本功能就足夠 Promise 的日常使用了,所以我們的模擬實現版的目標就是實現這些功能
模擬實現思路
第一步:骨架
Promise 的基本功能清楚了,那我們程式碼該怎麼寫,寫什麼?
從程式碼角度來看的話,無非也就是一些變數、函式,所以,我們就可以來針對各個功能點,思考下,都需要哪些程式碼:
- 變數上至少需要:三種狀態、當前狀態(_status)、傳遞給回撥函式的結果值(_value)
- 建構函式 constructor
task 處理函式- task 處理函式的兩個用於通知狀態變更的函式(handleResolve, handleReject)
- then 方法
- then 方法
註冊的兩個回撥函式 - 回撥函式佇列
- catch 方法
task 處理函式和註冊的回撥處理函式都是使用者在使用 Promise 時,自行根據業務需要編寫的程式碼
那麼,剩下的也就是我們在實現 Promise 時需要編寫的程式碼了,這樣一來,Promise 的骨架其實也就可以出來了:
export type statusChangeFn = (value?: any) => void;
/* 回撥函式型別 */
export type callbackFn = (value?: any) => any;
export class Promise {
/* 三種狀態 */
private readonly PENDING: string = 'pending';
private readonly RESOLVED: string = 'resolved';
private readonly REJECTED: string = 'rejected';
/* promise當前狀態 */
private _status: string;
/* promise執行結果 */
private _value: string;
/* 成功的回撥 */
private _resolvedCallback: Function[] = [];
/* 失敗的回撥 */
private _rejectedCallback: Function[] = [];
/**
* 處理 resolve 的狀態變更相關工作,引數接收外部傳入的執行結果
*/
private _handleResolve(value?: any) {}
/**
* 處理 reject 的狀態變更相關工作,引數接收外部傳入的失敗原因
*/
private _handleReject(value?: any) {}
/**
* 建構函式,接收一個 task 處理函式,task 有兩個可選引數,型別也是函式,其實也就是上面的兩個處理狀態變更工作的函式(_handleResolve,_handleReject),用來給使用者來觸發狀態變更使用
*/
constructor(task: (resolve?: statusChangeFn, reject?: statusChangeFn) => void) {}
/**
* then 方法,接收兩個可選引數,用於註冊成功或失敗時的回撥處理,所以型別也是函式,函式有一個引數,接收 Promise 執行結果或失敗原因,同時可返回任意值,作為新 Promise 的執行結果
*/
then(onResolved?: callbackFn, onRejected?: callbackFn): Promise {
return null;
}
catch(onRejected?: callbackFn): Promise {
return this.then(null, onRejected);
}
}
注意:骨架這裡的程式碼,我用了 TypeScript,這是一種強型別語言,可以標明各個變數、引數型別,便於講述和理解,看不懂沒關係,下面有編譯成 js 版的
所以,我們要補充完成的其實就是三部分:Promise 建構函式都做了哪些事、狀態變更需要做什麼處理、then 註冊回撥函式時需要做的處理
第二步:建構函式
Promise 的建構函式做的事,其實很簡單,就是馬上執行傳入的 task 處理函式,並將自己內部提供的兩個狀態變更處理的函式傳遞給 task,同時將當前 promise 狀態置為 PENDING(執行中)
constructor(task) {
// 1. 將當前狀態置為 PENDING
this._status = this.PENDING;
// 引數型別校驗
if (!(task instanceof Function)) {
throw new TypeError(`${task} is not a function`);
}
try {
// 2. 呼叫 task 處理函式,並將狀態變更通知的函式傳遞過去,需要注意 this 的處理
task(this._handleResolve.bind(this), this._handleReject.bind(this));
} catch (e) {
// 3. 如果 task 處理函式發生異常,當做失敗來處理
this._handleReject(e);
}
}
第三步:狀態變更
Promise 狀態變更的相關處理是我覺得實現 Promise 最難的一部分,這裡說的難並不是說程式碼有多複雜,而是說這塊需要理解透,或者看懂規範並不大容易,因為需要考慮一些處理,網上看了些 Promise 實現的文章,這部分都存在問題
狀態變更的工作,是由傳給 task 處理函式的兩個函式引數被呼叫時觸發進行,如:
new Promise((resolve, reject) => {
resolve(1);
});
resolve 或 reject 的呼叫,就會觸發 Promise 內部去處理狀態變更的相關工作,還記得建構函式做的事吧,這裡的 resolve 或 reject 其實就是對應著內部的 _handleResolve 和 _handleReject 這兩個處理狀態變更工作的函式
但這裡有一點需要注意,是不是 resolve 一呼叫,Promise 的狀態就一定發生變化了呢?
答案不是的,網上看了些這類文章,他們的處理是 resolve 呼叫,狀態就變化,就去處理回撥隊列了
但實際上,這樣是錯的
狀態的變更,其實依賴於 resolve 呼叫時,傳遞過去的引數的型別,因為這裡可以傳遞任意型別的值,可以是基本型別,也可以是 Promise
當型別不一樣時,對於狀態的變更處理是不一樣的,開頭那篇規範裡面有詳細的說明,但要看懂並不大容易,我這裡就簡單用我的理解來講講:
- resolve(x) 觸發的 pending => resolved 的處理:
- 當 x 型別是 Promise 物件時:
- 當 x 這個 Promise 的狀態變化結束時,再以 x 這個 Promise 內部狀態和結果(_status 和 _value)作為當前 Promise 的狀態和結果進行狀態變更處理
- 可以簡單理解成當前的 Promise 是依賴於 x 這個 Promise 的,即
x.then(this._handleResolve, this._handleReject)
- 當 x 型別是 thenable 物件(具有 then 方法的物件)時:
- 把這個 then 方法作為 task 處理函式來處理,這樣就又回到第一步即等待狀態變更的觸發
- 可以簡單理解成
x.then(this._handleResolve, this._handleReject)
- 這裡的 x.then 並不是 Promise 的 then 處理,只是簡單的一個函式呼叫,只是剛好函式名叫做 then
- 其餘型別時:
- 內部狀態(_status)置為 RESOLVE
- 內部結果(_value)置為 x
- 模擬建立微任務(setTimeout)處理回撥函式佇列
- 當 x 型別是 Promise 物件時:
- reject(x) 觸發的 pending => rejected 的處理:
- 不區分 x 型別,直接走 rejected 的處理
- 內部狀態(_status)置為 REJECTED
- 內部結構(_value)置為 x
- 模擬建立微任務(setTimeout)處理回撥函式佇列
- 不區分 x 型別,直接走 rejected 的處理
所以你可以看到,其實 resolve 即使呼叫了,但內部並不一定就會發生狀態變化,只有當 resolve 傳遞的引數型別既不是 Promise 物件型別,也不是具有 then 方法的 thenable 物件時,狀態才會發生變化
而當傳遞的引數是 Promise 或具有 then 方法的 thenable 物件時,差不多又是相當於遞歸回到第一步的等待 task 函式的處理了
想想為什麼需要這種處理,或者說,為什麼需要這麼設計?
這是因為,存在這樣一種場景:有多個非同步任務,這些非同步任務之間是同步關係,一個任務的執行依賴於上一個非同步任務的執行結果,當這些非同步任務通過 then 的鏈式呼叫組合起來時,then 方法產生的新的 Promise 的狀態變更是依賴於回撥函式的返回值。所以這個狀態變更需要支援當值型別是 Promise 時的非同步等待處理,這條非同步任務鏈才能得到預期的執行效果
當你們去看規範,或看規範的中文版翻譯,其實有關於這個的更詳細處理說明,比如開頭給的連結的那篇文章裡有專門一個模組:Promise 的解決過程,也表示成 [[Resolve]](promise, x)
就是在講這個
但我想用自己的理解來描述,這樣比較容易理解,雖然我也只能描述個大概的工作,更細節、更全面的處理應該要跟著規範來,下面就看看程式碼:
/**
* resolve 的狀態變更處理
*/
_handleResolve(value) {
if (this._status === this.PENDING) {
// 1. 如果 value 是 Promise,那麼等待 Promise 狀態結果出來後,再重新做狀態變更處理
if (value instanceof Promise) {
try {
// 這裡之所以不需要用 bind 來注意 this 問題是因為使用了箭頭函式
// 這裡也可以寫成 value.then(this._handleResole.bind(this), this._handleReject.bind(this))
value.then(v => {
this._handleResolve(v);
},
err => {
this._handleReject(err);
});
} catch(e) {
this._handleReject(e);
}
} else if (value && value.then instanceof Function) {
// 2. 如果 value 是具有 then 方法的物件時,那麼將這個 then 方法當做 task 處理函式,把狀態變更的觸發工作交由 then 來處理,注意 this 的處理
try {
const then = value.then;
then.call(value, this._handleResolve.bind(this), this._handleReject.bind(this));
} catch(e) {
this._handleReject(e);
}
} else {
// 3. 其他型別,狀態變更、觸發成功的回撥
this._status = this.RESOLVED;
this._value = value;
setTimeout(() = {
this._resolvedCallback.forEach(callback => {
callback();
});
});
}
}
}
/**
* reject 的狀態變更處理
*/
_handleReject(value) {
if (this._status === this.PENDING) {
this._status = this.REJECTED;
this._value = value;
setTimeout(() => {
this._rejectedCallback.forEach(callback => {
callback();
});
});
}
}
第四步:then
then 方法負責的職能其實也很複雜,既要返回一個新的 Promise,這個新的 Promise 的狀態和結果又要依賴於回撥函式的返回值,而回調函式的執行又要看情況是快取進回撥函式佇列裡,還是直接取依賴的 Promise 的狀態結果後,丟到微任務佇列裡去執行
雖然職能複雜是複雜了點,但其實,實現上,都是依賴於前面已經寫好的建構函式和狀態變更函式,所以只要前面幾個步驟實現上沒問題,then 方法也就不會有太大的問題,直接看程式碼:
/**
* then 方法,接收兩個可選引數,用於註冊回撥處理,所以型別也是函式,且有一個引數,接收 Promise 執行結果,同時可返回任意值,作為新 Promise 的執行結果
*/
then(onResolved, onRejected) {
// then 方法返回一個新的 Promise,新 Promise 的狀態結果依賴於回撥函式的返回值
return new Promise((resolve, reject) => {
// 對回撥函式進行一層封裝,主要是因為回撥函式的執行結果會影響到返回的新 Promise 的狀態和結果
const _onResolved = () => {
// 根據回撥函式的返回值,決定如何處理狀態變更
if (onResolved && onResolved instanceof Function) {
try {
const result = onResolved(this._value);
resolve(result);
} catch(e) {
reject(e);
}
} else {
// 如果傳入非函式型別,則將上個Promise結果傳遞給下個處理
resolve(this._value);
}
};
const _onRejected = () => {
if (onRejected && onRejected instanceof Function) {
try {
const result = onRejected(this._value);
resolve(result);
} catch(e) {
reject(e);
}
} else {
reject(this._value);
}
};
// 如果當前 Promise 狀態還沒變更,則將回調函式放入佇列裡等待執行
// 否則直接建立微任務來處理這些回撥函式
if (this._status === this.PENDING) {
this._resolvedCallback.push(_onResolved);
this._rejectedCallback.push(_onRejected);
} else if (this._status === this.RESOLVED) {
setTimeout(_onResolved);
} else if (this._status === this.REJECTED) {
setTimeout(_onRejected);
}
});
}
其他方面
因為目的在於理清 Promise 的主要功能職責,所以我的實現版並沒有按照規範一步步來,細節上,或者某些特殊場景的處理,可能欠缺考慮
比如對各個函式引數型別的校驗處理,因為 Promise 的引數基本都是函式型別,但即使傳其他型別,也仍舊不影響 Promise 的使用
比如為了避免被更改實現,一些內部變數可以改用 Symbol 實現
但大體上,考慮了上面這些步驟實現,基本功能也差不多了,重要的是狀態變更這個的處理要考慮全一點,網上一些文章的實現版,這個是漏掉考慮的
還有當面試遇到讓你手寫實現 Promise 時不要慌,可以按著這篇的思路,先把 Promise 的基本用法回顧一下,然後回想一下它支援的功能,再然後心裡有個大概的骨架,其實無非也就是幾個內部變數、建構函式、狀態變更函式、then 函式這幾塊而已,但死記硬背並不好,有個思路,一步步來,總能回想起來
原始碼
原始碼補上了 catch,resolve 等其他方法的實現,這些其實都是基於 Promise 基本功能上的一層封裝,方便使用
class Promise {
/**
* 建構函式負責接收並執行一個 task 處理函式,並將自己內部提供的兩個狀態變更處理的函式傳遞給 task,同時將當前 promise 狀態置為 PENDING(執行中)
*/
constructor(task) {
/* 三種狀態 */
this.PENDING = 'pending';
this.RESOLVED = 'resolved';
this.REJECTED = 'rejected';
/* 成功的回撥 */
this._resolvedCallback = [];
/* 失敗的回撥 */
this._rejectedCallback = [];
// 1. 將當前狀態置為 PENDING
this._status = this.PENDING;
// 引數型別校驗
if (!(task instanceof Function)) {
throw new TypeError(`${task} is not a function`);
}
try {
// 2. 呼叫 task 處理函式,並將狀態變更通知的函式傳遞過去,需要注意 this 的處理
task(this._handleResolve.bind(this), this._handleReject.bind(this));
} catch (e) {
// 3. 如果 task 處理函式發生異常,當做失敗來處理
this._handleReject(e);
}
}
/**
* resolve 的狀態變更處理
*/
_handleResolve(value) {
if (this._status === this.PENDING) {
if (value instanceof Promise) {
// 1. 如果 value 是 Promise,那麼等待 Promise 狀態結果出來後,再重新做狀態變更處理
try {
// 這裡之所以不需要用 bind 來注意 this 問題是因為使用了箭頭函式
// 這裡也可以寫成 value.then(this._handleResole.bind(this), this._handleReject.bind(this))
value.then(v => {
this._handleResolve(v);
},
err => {
this._handleReject(err);
});
} catch(e) {
this._handleReject(e);
}
} else if (value && value.then instanceof Function) {
// 2. 如果 value 是具有 then 方法的物件時,那麼將這個 then 方法當做 task 處理函式,把狀態變更的觸發工作交由 then 來處理,注意 this 的處理
try {
const then = value.then;
then.call(value, this._handleResolve.bind(this), this._handleReject.bind(this));
} catch(e) {
this._handleReject(e);
}
} else {
// 3. 其他型別,狀態變更、觸發成功的回撥
this._status = this.RESOLVED;
this._value = value;
setTimeout(() => {
this._resolvedCallback.forEach(callback => {
callback();
});
});
}
}
}
/**
* reject 的狀態變更處理
*/
_handleReject(value) {
if (this._status === this.PENDING) {
this._status = this.REJECTED;
this._value = value;
setTimeout(() => {
this._rejectedCallback.forEach(callback => {
callback();
});
});
}
}
/**
* then 方法,接收兩個可選引數,用於註冊回撥處理,所以型別也是函式,且有一個引數,接收 Promise 執行結果,同時可返回任意值,作為新 Promise 的執行結果
*/
then(onResolved, onRejected) {
// then 方法返回一個新的 Promise,新 Promise 的狀態結果依賴於回撥函式的返回值
return new Promise((resolve, reject) => {
// 對回撥函式進行一層封裝,主要是因為回撥函式的執行結果會影響到返回的新 Promise 的狀態和結果
const _onResolved = () => {
// 根據回撥函式的返回值,決定如何處理狀態變更
if (onResolved && onResolved instanceof Function) {
try {
const result = onResolved(this._value);
resolve(result);
} catch(e) {
reject(e);
}
} else {
// 如果傳入非函式型別,則將上個Promise結果傳遞給下個處理
resolve(this._value);
}
};
const _onRejected = () => {
if (onRejected && onRejected instanceof Function) {
try {
const result = onRejected(this._value);
resolve(result);
} catch(e) {
reject(e);
}
} else {
reject(this._value);
}
};
// 如果當前 Promise 狀態還沒變更,則將回調函式放入佇列裡等待執行
// 否則直接建立微任務來處理這些回撥函式
if (this._status === this.PENDING) {
this._resolvedCallback.push(_onResolved);
this._rejectedCallback.push(_onRejected);
} else if (this._status === this.RESOLVED) {
setTimeout(_onResolved);
} else if (this._status === this.REJECTED) {
setTimeout(_onRejected);
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
if (value instanceof Promise) {
return value;
}
return new Promise((reso) => {
reso(value);
});
}
static reject(value) {
if (value instanceof Promise) {
return value;
}
return new Promise((reso, reje) => {
reje(value);
});
}
}
測試
網上有一些專門測試 Promise 的庫,可以直接藉助這些,比如:promises-tests
我這裡就舉一些基本功能的測試用例:
- 測試鏈式呼叫
// 測試鏈式呼叫
new Promise(r => {
console.log('0.--同步-----');
r();
}).then(v => console.log('1.-----------------'))
.then(v => console.log('2.-----------------'))
.then(v => console.log('3.-----------------'))
.then(v => console.log('4.-----------------'))
.then(v => console.log('5.-----------------'))
.then(v => console.log('6.-----------------'))
.then(v => console.log('7.-----------------'))
輸出
0.--同步-----
1.-----------------
2.-----------------
3.-----------------
4.-----------------
5.-----------------
6.-----------------
7.-----------------
- 測試多次呼叫 then 註冊多個回撥處理
// 測試多次呼叫 then 註冊多個回撥處理
var p = new Promise(r => r(1));
p.then(v => console.log('1-----', v), err => console.error('error', err));
p.then(v => console.log('2-----', v), err => console.error('error', err));
p.then(v => console.log('3-----', v), err => console.error('error', err));
p.then(v => console.log('4-----', v), err => console.error('error', err));
輸出
1----- 1
2----- 1
3----- 1
4----- 1
- 測試非同步場景
// 測試非同步場景
new Promise(r => {
r(new Promise(a => setTimeout(a, 5000)).then(v => 1));
})
.then(v => {
console.log(v);
return new Promise(a => setTimeout(a, 1000)).then(v => 2);
})
.then(v => console.log('success', v), err => console.error('error', err));
輸出
1 // 5s 後才輸出
success 2 // 再2s後才輸出
這個測試,可以檢測出 resolve 的狀態變更到底有沒有根據規範,區分不同場景進行不同處理,你可以網上隨便找一篇 Promise 的實現,把它的程式碼貼到瀏覽器的 console 裡,然後測試一下看看,就知道有沒有問題了
- 測試執行結果型別為 Promise 物件場景
// 測試執行結果型別為 Promise 物件場景(Promise 狀態 5s 後變化)
new Promise(r => {
r(new Promise(a => setTimeout(a, 5000)));
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success undefined // 5s 後才輸出
// 測試執行結果型別為 Promise 物件場景(Promise 狀態不會發生變化)
new Promise(r => {
r(new Promise(a => 1));
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
// 永遠都不輸出
- 測試執行結果型別為具有 then 方法的 thenable 物件場景
// 測試執行結果型別為具有 then 方法的 thenable 物件場景(then 方法內部會呼叫傳遞的函式引數)
new Promise(r => {
r({
then: (a, b) => {
return a(1);
}
});
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success 1
// // 測試執行結果型別為具有 then 方法的 thenable 物件場景(then 方法內部不會呼叫傳遞的函式引數)
new Promise(r => {
r({
then: (a, b) => {
return 1;
}
});
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
// 永遠都不輸出
// 測試執行結果型別為具有 then 的屬性,但屬性值型別非函式
new Promise(r => {
r({
then: 111
});
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success {then: 111}
- 測試執行結果的傳遞
// 測試當 Promise rejectd 時,reject 的狀態結果會一直傳遞到可以處理這個失敗結果的那個 then 的回撥中
new Promise((r, j) => {
j(1);
}).then(v => console.log('success', v))
.then(v => console.log('success', v), err => console.error('error', err))
.catch(err => console.log('catch', err));
輸出
error 1
// 測試傳給 then 的引數是非函式型別時,執行結果和狀態會一直傳遞
new Promise(r => {
r(1);
}).then(1)
.then(null, err => console.error('error', err))
.then(v => console.log('success', v), err => console.error('error', err));
輸出
success 1
// 測試 rejectd 失敗被處理後,就不會繼續傳遞 rejectd
new Promise((r,j) => {
j(1);
}).then(2)
.then(v => console.log('success', v), err => console.error('error', err))
.then(v => console.log('success', v), err => console.error('error', err));
輸出
error 1
success undefined
最後,當你自己寫完個模擬實現 Promise 時,你可以將程式碼貼到瀏覽器上,然後自己測試下這些用例,跟官方的 Promise 執行結果比對下,你就可以知道,你實現的 Promise 基本功能上有沒有問題了
當然,需要更全面的測試的話,還是得藉助一些測試庫
不過,自己實現一個 Promise 的目的其實也就在於理清 Promise 基本功能、行為、原理,所以這些用例能測通過的話,那麼基本上也就掌握這些知識點