遵循Promises/A+規範,深入分析Promise實現細節 | 通過872測試樣例
前言
本週寫文的核心為 Promise
,Promise
大家應該都特別熟悉了,Promise
是非同步程式設計的一種解決方案,廣泛用在日常程式設計中。本週小包將圍繞 Promise
原始碼手寫進行寫文,原始碼手寫初步計劃使用三篇文章實現—— 手寫 Promise
之基礎篇,手寫 Promise
之 resolvePromise
篇,手寫 Promise
之靜態方法篇。
Promises/A+ 規範是 Promise
的實現準則,因此 Promise
手寫系列將遵循 Promises/A+
規範的思路,以案例和提問方式層層深入,一步一步實現 Promise
封裝。
學習本文,你能收穫:
- 🌟 理解
Promise A+
規範 - 🌟 理解什麼是
Promise
的值穿透、Promise
鏈式呼叫機制、Promise
註冊多個then
方法等。 - 🌟 掌握
Promise
原始碼編寫全過程 - 🌟 掌握髮布訂閱模式在
Promise
原始碼編寫中的使用
基礎鋪墊
Promise
必定處於下列三種狀態之一:
Pending
等待態: 初始狀態,不是成功或失敗狀態。Fulfilled
完成態: 意味著操作成功完成。Rejected
失敗態: 意味著操作成功失敗。- 當
promise
處於Pending
狀態時,可以轉變為Fulfilled
或者Rejected
當
promise
處於Fulfilled
或Rejected
時,狀態不能再發生改變
那什麼會觸發 promise
中狀態的改變吶?我們來看幾個栗子:
// p1 什麼都不執行且傳入空函式
const p1 = new Promise(() => {});
console.log("p1: ", p1);
// p2 執行 resolve
const p2 = new Promise((resolve, reject) => {
resolve("success");
});
console.log("p2: ", p2);
// p3 執行 reject
const p3 = new Promise((resolve, reject) => {
reject("fail");
});
console.log("p3: ", p3);
// p4 丟擲錯誤
const p4 = new Promise((resolve, reject) => {
throw Error("error");
});
console.log("p4: ", p4);
// p5 先執行 resolve 後執行 reject
const p5 = new Promise((resolve, reject) => {
resolve("success");
reject("fail");
});
console.log("p5: ", p5);
// p6 什麼都不執行且不傳參
const p6 = new Promise();
console.log("p6: ", p6);
複製程式碼
我們來看一下輸出結果:
從輸出結果我們可以發現:
- 建立
promise
物件時,需傳入一個函式(否則會報錯,詳見 p6),並且該函式會立即執行 promise
的初始狀態為Pending
(見 p1)- 執行
resolve()
和reject()
可以將promise
的狀態修改為Fulfilled
和Rejected
(見 p2,p3) - 若
promise
中丟擲異常,相當於執行reject
(見 p4) promise
狀態轉變只能由Pending
開始(見 p5)
根據我們對輸出結果的分析,我們來編寫 promise
的第一版程式碼。
實現基礎 promise —— 第一版
promise 建構函式實現
- 首先定義
promise
的三種狀態
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
複製程式碼
- 定義
Promise
建構函式,新增必備屬性
Promises/A+
規範中指出:
value
是任意的JavaScript
合法值(包括undefined
)reason
是用來表示promise
為什麼被拒絕的原因
我們使用 ES6 class
定義 Promise
類,value/reason
分別賦值為 undefined
,狀態 status
初始為 PENDING
class Promise {
constructor() {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
}
}
複製程式碼
- 定義
promise
時需要傳入函式executor
executor
有兩個引數,分別為resolve,reject
,且兩個引數都是函式executor
會立即執行
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
// 定義resolve 和 reject 函式
const resolve = () => {};
const reject = () => {};
// 構造器立即執行
executor(resolve, reject);
}
}
複製程式碼
- 實現
resolve
和reject
的功能
當 promise
狀態為 Pedding
時: resolve
函式可以將 promise
由 Pending
轉變為 Fulfilled
,並且更新 promise
的 value
值。reject
函式可以將 promise
由 Pending
轉變為 Rejected
,並且更新 promise
的 reason
值
注意:
promise
狀態只能由Pending -> Fulfilled
和Pending -> Rejected
因此在定義 resolve
和 reject
函式時,內部需要先判斷 promise
的狀態,如果狀態為 pending
,才可以更新 value
值和 promise
狀態。
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = (value) => {
// 判斷當前的狀態是否為Pending
// promise狀態轉變只能從 Pending 開始
if (this.status === PENDING) {
// 更新 value 值和 promise 狀態
this.value = value;
this.status = FULFILLED;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
executor(resolve, reject);
}
}
複製程式碼
原始碼寫到這裡,小包就產生疑惑了本文第一個疑問,來,上問題。
提問一: resolve/reject 函式為什麼使用箭頭函式定義?
問題答案小包這裡先不講,大家先思考思考,到文末小包一塊回答。
Promise A+
規範規定,Promise
執行丟擲異常時,執行失敗函式。因此我們需要捕獲executor
的執行,如果存在異常,執行reject
函式。
class Promise {
// ...多餘程式碼先暫省略
// 捕獲 executor 異常
try {
executor(resolve, reject);
} catch (e) {
// 當發生異常時,呼叫 reject 函式
reject(e);
}
}
}
複製程式碼
我們實現完了 Promise
的主體部分,下面就來實現 Promise
的另一重要核心 then
方法。
實現 then 方法的基本功能
then
方法的注意事項比較多,咱們一起來閱讀規範順帶舉例說明一下。
promise.then
接受兩個引數:
promise.then(onFulfilled, onRejected);
複製程式碼
定義 then
函式,接收兩個引數
class Promise {
then (onFulfilled, onRejected) {}
}
複製程式碼
onFulfilled
和onRejected
是可選引數,兩者如果不是函式,則會忽略掉(真的是簡單的忽略掉嗎?請看下文值穿透)- 如果
onFulfilled
是一個函式,當promise
狀態為Fulfilled
時,呼叫onFulfilled
函式,onRejected
類似,當promise
狀態為Rejeted
時呼叫。
我們繼續來看幾個栗子:
// 執行 resolve
const p1 = new Promise((resolve, reject) => {
resolve(1);
});
p1.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
// 執行 reject
const p2 = new Promise((resolve, reject) => {
reject(2);
});
p2.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
// 丟擲異常
const p3 = new Promise((resolve, reject) => {
throw new Error("promise執行出現錯誤");
});
p3.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
複製程式碼
我們來看一下輸出結果:
通過輸出結果,我們可以發現 then
的呼叫邏輯
- 執行
resolve
後,promise
狀態改變為Fulfilled
,onFulfilled
函式呼叫,引數值為value
。 - 執行
reject
或 丟擲錯誤,promise
狀態改變為Rejected
,onRejected
函式呼叫,引數值為reason
。
接下來,我們來分析一下 then
的實現思路。
then
函式中判斷 promise
當前的狀態,如果為 Fulfilled
狀態,執行 onFulfilled
函式;Rejected
狀態,執行 onRejected
函式。實現思路很簡單,那下面咱們就來實現一下。
class Promise {
then(onFulfilled, onRejected) {
// 當狀態為 Fulfilled 時,呼叫 onFulfilled函式
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
// 當狀態為 Rejected 時,呼叫 onRejected 函式
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
複製程式碼
提問二: then 方法執行時 promise 狀態會出現 Pending 狀態嗎
promise 註冊多個 then 方法
我們繼續往下讀規範:
如果一個 promise
呼叫多次 then
: 當 promise
狀態為 Fulfilled
時,所有的 onFulfilled
函式按照註冊順序呼叫。當 promise
狀態為 Rejected
時,所有的 onRejected
函式按照註冊順序呼叫。
這個規範講的是什麼意思那?小包來舉個栗子:
const p = new Promise((resolve, reject) => {
resolve("success");
});
p.then((v) => {
console.log(v);
});
p.then((v) => {
console.log(`${v}--111`);
});
複製程式碼
輸出結果:
success;
success---111;
複製程式碼
通過上面的案例,該規範通俗來講: 同一個 promise 可以註冊多個 then 方法,當 promise 完成或者失敗後,對應的 then 方法按照註冊順序依次執行。
該規範咱們的程式碼已經可以相容。學到這裡,我們整合一下 Promise
第一版程式碼,並對目前所寫程式碼進行測試。
// promise 三種狀態
// 狀態只能由 PENDING -> FULFILLED/REJECTED
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
// 初始狀態為 Pending
this.status = PENDING;
// this指向問題
const resolve = (value) => {
// 判斷當前的狀態是否為Pending
// promise狀態轉變只能從 Pending 開始
if (this.status === PENDING) {
// 更新 value 值和 promise 狀態
this.value = value;
this.status = FULFILLED;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
try {
// 捕獲 executor 異常
executor(resolve, reject);
} catch (e) {
// 當發生異常時,呼叫 reject 函式
reject(e);
}
}
then(onFulfilled, onRejected) {
// 當狀態為 Fulfilled 時,呼叫 onFulfilled函式
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
// 當狀態為 Rejected 時,呼叫 onRejected 函式
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
複製程式碼
先來測試基礎部分的案例,輸出結果如下:
再來測試同一 Promise
註冊多個 then
方法,輸出結果為
success;
success---111;
複製程式碼
第一版程式碼是可以滿足當前規範的,~~~,放鬆一下,我們來繼續實現。
處理非同步功能——第二版
文章剛開始我們就講過,promise
是非同步程式設計的一種解決方案,那我們來測試一下第一版 Promise
是否可以實現非同步。
const p = new Promise((resolve, reject) => {
// 使用 setTimeout 模擬一下非同步
setTimeout(() => {
resolve("success");
});
});
p.then((v) => {
console.log(v);
});
p.then((v) => {
console.log(`${v}--111`);
});
複製程式碼
沒有任何輸出,可見第一版程式碼到目前是無法實現非同步程式設計的,我們來分析一下原因。
如果 Promise
內部存在非同步呼叫,當執行到 then
函式時,此時由於 resolve/reject
處於非同步回撥之中,被阻塞未能呼叫,因此 promise
的狀態仍為 Pending
,第一版 then
回撥中的 onFulfilled
和 onRejected
無法執行。
釋出訂閱模式
為了更好的實現原生 promise
的編寫,在這裡我們插補一點知識。
非同步程式設計中有一個經常使用的思想,叫做釋出訂閱模式。釋出訂閱模式是指基於一個事件(主題)通道,希望接收通知的物件 Subscriber
通過自定義事件訂閱主題,被啟用事件的物件 Publisher
通過釋出主題事件的方式通知各個訂閱該主題的 Subscriber
物件。
釋出訂閱模式中有三個角色,釋出者 Publisher
,事件通道 Event Channel
,訂閱者 Subscriber
。
光憑藉定義有點難以理解,小包舉一個栗子: 以目前的熱播劇人世間為例,人世間實在太火了,工作時候也安不下心,每天就迫不及待的等人世間更新,想在人世間更新的第一刻就開始看劇,那你應該怎麼做吶?總不能時時刻刻重新整理頁面,監測人世間是否更新。平臺是人性化的,其提供了訊息訂閱功能,如果你選擇訂閱,平臺更新人世間後,會第一時間發訊息通知你,訂閱後,你就可以愉快的追劇了。
那我們要怎麼設計 Promise
的非同步功能吶? 我們把 Promise
的功能按照發布訂閱模式分解一下:
then
回撥onFulfilled/onRejected
函式resolve/reject
函式resolve/reject
函式執行後,promise
狀態改變,then
回撥函式執行
只有當 resolve/reject
函式執行後,對應 onFulfilled/onRejected
才可以執行執行,但由於存在非同步呼叫,resolve/reject
執行晚於 then
函式。因此 onFulfilled/onRejected
就可以理解為訂閱者,訂閱 resolve/reject
函式執行;resolve/reject
是釋出者;Promise
提供事件通道作用,儲存訂閱的 onFulfilled/onRejected
。由於同一個 promise 物件可以註冊多個 then 回撥,因此 Event Channel 儲存回撥應為陣列格式
因此我們需要修改 resolve/reject
函式的實現,當兩者被呼叫時,同時通知對應訂閱者執行。
非同步實現
- 在
Promise
中定義兩個陣列onFulfilledCallbacks
和onRejectedCallbacks
,分別用來儲存then
回撥onFulfilled
和onRejected
函式
class Promise {
// 儲存訂閱的onFulfilled函式和onRejected函式
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
}
複製程式碼
then
方法執行時,若Promise
處於Pending
狀態,將onFulfilled
和onRejected
函式分別訂閱至onFulfilledCallbacks
和onRejectedCallbacks
——等待resolve/reject
執行(事件釋出)
then(onFulfilled, onRejected) {
if (this.status === PENDING) {
// 當promise處於pending狀態時,回撥函式訂閱
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
複製程式碼
- 呼叫
resolve/reject
時,釋出事件,分別執行對應onFulfilledCallbacks
和onRejectedCallbacks
陣列中的函式
// 執行釋出
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
// 依次執行onFulfilled函式
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 依次執行onRejected函式
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
複製程式碼
我們將上述程式碼進行彙總,形成第二版程式碼,並進行案例測試。
// 非同步呼叫
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
// 儲存訂閱的onFulfilled函式和onRejected函式
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
// 當 resolve 函式呼叫時,通知訂閱者 onFulfilled 執行
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 當 reject 函式呼叫時,通知訂閱者 onRejected 執行
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
// 當promise處於pending狀態時,回撥函式訂閱
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
複製程式碼
使用剛才的案例進行測試,輸出結果為
success
success--111
複製程式碼
上面的案例有些簡單,我們再來測試一個複雜的案例:
console.log(1);
setTimeout(() => {
console.log(2);
})
const p1 = new Promise((resolve) => {
console.log(3);
setTimeout(() => {
resolve(4);
})
})
p1.then(v => console.log(v));
console.log(5);
複製程式碼
瀏覽器輸出結果:
第二版程式碼輸出結果:
瀏覽器與第二版輸出的結果是相同的,因此可見目前第二版 Promise
是可以實現非同步功能的。
但真的沒問題嗎?我們把案例稍微修改,去掉 Promise
中的非同步呼叫,看瀏覽器輸出結果是否與第二版相同。
console.log(1);
setTimeout(() => {
console.log(2);
})
const p1 = new Promise((resolve) => {
console.log(3);
resolve(4);
})
p1.then(v => console.log(v));
console.log(5);
複製程式碼
瀏覽器輸出結果:
第二版程式碼輸出結果:
我們可以明顯的發現第二版程式碼與瀏覽器的2 4 輸出是相反的?可見瀏覽器中先執行 then
方法,後執行 setTimeout
?
提問三: 為什麼瀏覽器會先執行 then 方法回撥,後執行 setTimeout 那?
鏈式呼叫——第三版
非同步功能實現完畢,我們繼續去實現 then
方法的鏈式呼叫。首先我們繼續去讀規範:
then
方法必須返回一個promise
promise2 = promise1.then(onFulfilled, onRejected)
複製程式碼
promise2
是 then
函式的返回值,同樣是一個 Promise
物件。
then(onFulfilled, onRejected) {
// ... 多餘程式碼省略
cosnt promise2 = new Promise((resolve, reject) => {})
return promise2;
}
複製程式碼
- 如果
onFulfilled
或onRejected
返回值為x
,則執行Promise Resolution Procedure [[Resolve]](promise2, x)
(這裡暫且將他理解為執行 promise2 的 resolve(x)函式)
我們來舉栗子理解一下此條規範:
// 案例1 resolve
console.log(new Promise((resolve) => {
resolve(1)
}).then((x) => x))
// 案例2 reject
console.log(new Promise((resolve, reject) => {
reject(1)
}).then(undefined,(r) => r))
複製程式碼
咦,怎麼兩者返回結果一樣,明明 promise
中分別執行 resolve
和 reject
函式。
我們再來詳讀一遍規範:
- 如果
onFulfilled
或onRejected
返回值為x
——上面兩個函式都(v) => v
,傳入引數值都是1
,因此返回值x = 1
; - 則執行
promise2
的resolve(x)
函式,然後then
返回promise2
物件——因此上面兩個函式都是呼叫promise2
的resolve
函式,所以兩者返回值都是處於fulfilled
狀態的promise
物件,並且值都為1
。
由於我們需要將 onFulfilled/onRejected
函式返回值作為 promise2 resolve
的引數值,因此我們需要將 then
函式整體移動至 promise2
內部。
then (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 返回值作為 resolve 的引數值
let x = onFulfilled(this.value);
resolve(x);
}
if (this.status === REJECTED) {
let x = onRejected(this.reason);
resolve(x);
}
});
return promise2;
}
複製程式碼
你以為這樣就能實現這條規範了嗎?NONONO!!!
難點: 同步程式碼上述思路的確可以實現,但設想這樣一個場景,若 Promise
中存在非同步程式碼,非同步邏輯設計是 then
執行時,若 Promise
處於 Pending
狀態,先將 onFulfilled/onRejected
函式訂閱到 onFulfilledCallbacks/onRejectedCallbacks
中,意味著在 then
中此時兩函式不會執行,那麼此我們應該如何獲取兩者的返回值那?
因此我們不能單純的使用 this.onFulfilledCallbacks.push(onFulfilled)
將回調函式壓入事件通道的儲存陣列中,我們對回撥函式做一層封裝,將 promise2
的 resolve
函式和 onFulfilled
封裝在一起,這樣當 onFulfilled
執行時,可以獲取其返回值 x
,返回 fulfilled
狀態的 promise2
,具體可以看下面程式碼:
// 使用匿名箭頭函式,保證內部 this 指向
() => {
// 回撥函式執行,獲取其返回值
let x = onFulfilled(this.value);
// 執行 promise2 的 resolve 方法
resolve(x);
}
複製程式碼
因此 Pending
狀態的程式碼如下:
if (this.status === PENDING) {
// 使用匿名函式,將 resovle 與 onFulfilled 捆綁在一起
this.onFulfilledCallbacks.push(() => {
let x = onFulfilled(this.value);
resolve(x);
});
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason);
resolve(x);
});
}
複製程式碼
- 如果
onFulfilled
或onRejected
執行過程中丟擲異常e
,則呼叫promise2
的reject(e)
,返回promise2
我們還是舉栗子測試一下:
console.log(new Promise((resolve) => {
resolve(1)
}).then(()=> {
throw new Error('resolve err')
}))
console.log(new Promise((resolve, reject) => {
reject(1)
}).then(undefined,()=> {
throw new Error('reject err')
}))
複製程式碼
通過輸出結果,我們可以看出當 onFulfilled/onRejected
函式報錯時,promise2
會執行其 reject
函式。因此我們需要給目前的程式碼新增一層異常捕獲,將程式碼修改成如下情況:
then(onFulfilled, onRejected) {
let p1 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 新增異常捕獲
try {
// 返回值作為 resolve 的引數值
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
}
//... 其餘部分類似
return promise2;
}
複製程式碼
- 如果
onFulfilled
不是函式,且promise
狀態為Fulfilled
,那麼promise2
應該接受同樣的值,同時狀態為Fulfilled
這個規範是啥意思吶?我們來舉一個栗子:
// 輸出結果 1
const p1 = new Promise((resolve) => {
resolve(1)
})
p1.then(x => x).then().then().then().then().then(x=> console.log(x))
複製程式碼
上述程式最終輸出結果為 1
,初次 resolve
傳遞的 value
值為 1
,可見當 onFulfilled
不是函式時, promise
值會沿 then
發生傳遞,直到 onFulfilled
為函式。
這也就是 Promise
的值傳遞,當 then
的 onFulfilled
為非函式時,值會一直傳遞下去,直至遇到函式 onFulfilled
- 如果
onRejected
不是函式,且promise
狀態為Rejected
,那麼promise2
應該接受同樣的原因,同時狀態為Rejected
// 輸出結果 Error: error at <anonymous>:4:33
const p1 = new Promise((resolve) => {
reject(1)
})
p1.then(undefined, () => {throw Error('error')}).then().then().then().then().then(x=> console.log(x), (r)=> console.log(r))
複製程式碼
與 onFulfilled
類似,Promise
同樣提供了對onRejected
函式的相容,會發生錯誤傳遞。
通過第 4 條與第 5 條的案例,我們可以發現,當 onFulfilled/onRejected
為非函式型別,Promise
會分別發生值傳遞和異常傳遞。
我們如何才能連續傳遞值或者異常那?(見下面程式碼)
- 值傳遞: 值傳遞非常簡單,我們只需要定義一個函式,引數值為 x ,返回值為 x
- 異常: 定義函式引數值為異常,之後不斷丟擲此異常。
x => x;
e => throw e;
複製程式碼
then(onFulfilled, onRejected) {
// 判斷引數是否為函式,如果不是函式,使用預設函式替代
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (e) => {
throw e;
};
let promise2 = new Promise((resolve, reject) => {
});
return promise2;
}
複製程式碼
寫到這裡,鏈式呼叫的部分就暫時實現了,我們整合一下第三版 Promise
程式碼。
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (e) => {
throw e;
};
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 新增異常捕獲
try {
// 返回值作為 resolve 的引數值
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === PENDING) {
// 使用匿名函式,將 resovle 與 onFulfilled 捆綁在一起
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
});
}
});
return promise2;
}
}
複製程式碼
我們測試一下是否可以實現鏈式呼叫:
// 輸出結果為 4,可以說明resolve狀態的鏈式呼叫是可行的,並且實現了值傳遞
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
p1.then((v) => v + 1)
.then((v) => v * 2)
.then()
.then((v) => console.log(v));
// 輸出 Error1,說明鏈式呼叫仍然是成功的。
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
});
});
p2.then(
() => {},
(r) => new Error(r)
).then(
(v) => console.log("v", v),
(r) => console.log("r", r)
);
複製程式碼
寫到這裡,第三版程式碼就實現成功了,後面還有最核心的 resolvePromise
部分,該部分比較複雜,因此小包決定專門開一篇文章詳細講述。
問題回答
resolve/reject 函式為什麼使用箭頭函式定義?
一句話解釋: this
指向問題。
我們將其修改為普通函式形式:
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
const resovle = function (value) {
console.log(this);
this.value = value;
}
const reject = (reason) => {
console.log(this);
this.reason = reason;
}
executor(resovle, reject)
}
}
複製程式碼
之後我們分別執行以下程式碼:
var value = 1;
new Promise((resolve, reject) => {
resolve(100)
})
複製程式碼
從結果我們可以發現: this
的輸出結果為 undefined
。因為 resolve
是一個普通函式,在 Promise
中呼叫為預設呼叫,this
非嚴格模式指向 window
,嚴格模式指向 undefined
。 ES6 class
預設為嚴格模式,因此指向 undefined
。所以使用普通函式,我們獲取不到 Promise
中的 value
屬性。
// 輸出結果 Promise {value: undefined, reason: 200}
var reason = 2;
new Promise((resolve, reject) => {
reject(200)
})
複製程式碼
reject
使用箭頭函式,箭頭函式自身沒有 this
,因此會沿作用域鏈使用外層作用域的 this
。所以我們可以獲取到 reason
屬性。
then 方法執行時 promise 狀態會出現 pending 狀態嗎
會出現,文章中已經提到了,當 Promise 中存在非同步程式碼時,例如
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
})
})
複製程式碼
為什麼瀏覽器會先執行 then 方法回撥,後執行 setTimeout 那?
這是 JavaScript
的事件機制(Event Loop)導致的,then
回撥為微任務,setTimeout
為巨集任務,當同步程式碼執行完畢後,主程式會先去微任務佇列尋找任務,微任務佇列全部執行完畢,才會執行巨集任務佇列。
最後
如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑
如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源專案點點star:http://github.crmeb.net/u/defu不勝感激 !
PHP學習手冊:http://doc.crmeb.com
技術交流論壇:http://q.crmeb.com
- 遵循Promises/A 規範,深入分析Promise實現細節 | 通過872測試樣例
- 80 行程式碼實現簡易 RxJS
- 前後端分離專案,如何解決跨域問題?
- springboot中攔截並替換token來簡化身份驗證
- 15 行程式碼在 wangEditor v5 使用數學公式
- Java執行緒池必知必會
- EdgeDB 架構簡析
- TS 型別體操:圖解一個複雜高階型別
- 基於babel的埋點工具簡單實現及思考
- 使用craco對cra專案進行構建優化
- Netty核心概念之ChannelHandler&Pipeline&ChannelHandlerContext
- 理解python非同步程式設計與簡單實現asyncio
- Mycat 作為代理服務端的小知識點
- 一文吃透 React Expiration Time
- 前端模組化詳解
- Java必備主流技術流程圖
- 【建議使用】告別if,Java超好用引數校驗工具類
- MySQL模糊查詢再也不用like %了
- Java 8 的Stream流那麼強大,你知道它的原理嗎
- Vue SEO的四種方案