溫故知新
上一篇《如何從無到有實現Promise(上)》中我們已經實現了一個看似可以正常工作的簡易版 Promise 凯旭,不要認為這樣就結束了评疗,其實好戲才剛剛開始。
本篇我們繼續(xù)改造和豐富這個 Promise,讓它可以適用更復雜的場景。
本文篇幅較長旁壮,又有大量的代碼片段,可以邊記錄筆記邊閱讀谐檀,對照著看更容以理解。
鏈式調用“有點東西”
眾所周知 Promise 的 then 方法是支持鏈式調用的裁奇,如下所示:
- 鏈式調用場景1
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return "second then";
})
.then((data) => {
console.log(data);
});
輸出結果應該為桐猬,先打印 first then ,兩秒后打印 second then刽肠。
但是我們目前實現的 Promise 是不支持的溃肪,那該如何實現 Promise 實例的 then 方法支持鏈式調用這個行為呢免胃?
在 Promise 實例的 then 方法內的 onfulfilled 、onrejected 函數中惫撰,是支持再次返回一個 Promise 實例的羔沙,也支持返回一個普通值(非 Promise 實例);并且返回的這個 Promise 實例或者這個普通值將會傳給下一個 then 方法里 onfulfilled 厨钻、onrejected 函數中扼雏,如此, then 方法就支持了鏈式調用夯膀。
如上總結诗充,想要支持 then 的鏈式調用,就要每一個 then 方法的 onfulfilled 函數和 onrejected 函數都要返回一個 Promise 實例诱建。
我們先支持鏈式調用 then 方法時返回一個普通的值的情況蝴蜓,改造 then 方法:
Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
let promise2;
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
};
結合我們上面的分析,代碼不難理解俺猿,當調用 Promise 實例 then 方法的時候茎匠,應該再次返回一個 Promise 實例,promise2 就將作為 then 方法的返回值押袍。
這個 promise2 是什么時候被 resolve 或者 reject 的呢?
- 當判斷分支是
status === "fulfilled"
或者status === "rejected"
的時候诵冒!
這個 promise2 在執(zhí)行完 then 方法時就被 resolve 或者 reject 了。
- 當判斷分支為
status === "pending"
時情況較為復雜伯病!
返回的 promise2 實例的 resolve 和 reject 是放到 onFulfilledArray 和 onRejectedArray數組中的造烁。
當異步執(zhí)行完成后,依次執(zhí)行 onFulfilledArray 和 onRejectedArray 數組內的函數時才執(zhí)行了 promise2 的 resolve午笛、reject惭蟋。
那么在 onFulfilledArray 和 onRejectedArray 數組中的函數內應該 resolve 或是 reject 掉 promise2,并且傳入的參數就是 onfulfilled 或者 onrejected 的執(zhí)行結果药磺。
這樣 then 方法支持鏈式調用告组,并且支持返回一個普通值的情況。
再來整體看下目前為止的完整代碼:
function Promise(excutor) {
this.status = "pending";
this.resolveVal = null;
this.rejectVal = null;
this.onFulfilledFuncArray = [];
this.onRejectedFuncArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === "pending") {
this.resolveVal = value;
this.status = "fulfilled";
this.onFulfilledFuncArray.forEach((fn) => {
fn(this.resolveVal);
});
}
}, 0);
};
const reject = (error) => {
setTimeout(() => {
if (this.status === "pending") {
this.rejectVal = error;
this.status = "rejected";
this.onRejectedFuncArray.forEach((fn) => {
fn(this.rejectVal);
});
}
}, 0);
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
Promise.prototype.then = function (
onfulfilled = Function.prototype,
onrejected = Function.prototype
) {
let promise2;
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.resolveVal);
resolve(result);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.rejectVal);
resolve(result);
} catch (e) {
reject(e);
}
});
}));
}
};
上面也說了癌佩,then 方法也是支持顯式返回一個 Promise 實例的情況木缝,如下。
- 鏈式調用場景2
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("second then");
}, 2000);
});
})
.then((data) => {
console.log(data);
});
第一種情況是 then 方法的 onFulfilled 和 onrejected 函數返回的是一個普通值围辙,與之不同的是這里我們要支持 onFulfilled 和 onrejected 函數返回的是一個 Promise 實例我碟。
let result = onfulfilled(this.resolveVal);
let result = onrejected(this.rejectVal);
場景1中,上面兩行代碼得到的結果都是一個普通值姚建,也就是說 result 就是一個普通值矫俺,現在要做的是讓 result 可以為普通值,也可以為 Promise 實例。所以我們就不能直接對 result 進行 resolve(result) 的操作厘托。
綜上所述友雳,我們抽象出 resolvePromise 方法進行統(tǒng)一處理,替換原 resolve 方法铅匹。
接下來就要完成這個 resolvePromise 函數:
首先我們定義方法參數:
- promise2:返回的 Promise 實例
- result:onfulfilled 或者 onrejected 函數的返回值
- resolve: promise2 的 resolve 方法
- reject: promise2 的 reject 方法
方法實現為:
const resolvePromise = (promise2, result, resolve, reject) => {
if ((typeof result === "function" || typeof result === "object") && result !== null) {
try {
if (typeof result.then === "function") {
result.then.call(
result,
function (value) {
return resolvePromise(promise2, value, resolve, reject);
},
function (error) {
return reject(error);
}
);
} else {
resolve(result);
}
} catch (e) {
return reject(e);
}
} else {
resolve(result);
}
};
看不懂不要急押赊,此處是本文最大的難點了,也是鏈式調用的核心所在包斑,接下來將目前的代碼整合在一起流礁,然后仔細分析下 resolvePromise 究竟做了什么。
附加注釋的完整代碼如下:
function Promise(executor) {
this.status = "pending";
this.resolveVal = null;
this.rejectVal = null;
this.onFulfilledArray = [];
this.onRejectedArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === "pending") {
this.resolveVal = value;
this.status = "fulfilled";
this.onFulfilledArray.forEach((fn) => {
fn(this.resolveVal);
});
}
});
};
const reject = (error) => {
setTimeout(() => {
if (this.status === "pending") {
this.rejectVal = error;
this.status = "rejected";
this.onRejectedArray.forEach((fn) => {
fn(this.rejectVal);
});
}
});
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// 參數1:promise2實例 參數2:onfulfilled 和 onrejected 執(zhí)行結果 參數3舰始、4:promise2 的 resolve 以及 reject
const resolvePromise = (promise2, result, resolve, reject) => {
// 如果返回數據可能是 Promise 類型(再進行 .then 判斷后才可真正確認崇棠,在下面處理)
if (
(typeof result === "function" || typeof result === "object") &&
result !== null
) {
try {
// 通過 then 方法判斷可以確定是否是 Promise 類型
if (typeof result.then === "function") {
// 執(zhí)行 then 方法 , 參數分別為 onfulfilled 和 onrejected
result.then.call(
result,
function (value) {
// onfulfilled 函數
// 當 result 是 Promise 類型時丸卷,遞歸調用 resolvePromise 函數枕稀,直到 result 不再是 Promise 類型,執(zhí)行 promise2 (當前then的返回promise)的 resolve谜嫉。
return resolvePromise(promise2, value, resolve, reject);
},
function (error) {
// onrejected 函數
return reject(error);
}
);
} else {
// result 不是Promise類型萎坷,直接 resolve promise2
resolve(result);
}
} catch (e) {
return reject(e);
}
} else {
// result 不是Promise類型,直接 resolve promise2
resolve(result);
}
};
Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
// Promise 需要支持鏈式調用沐兰,所以 then 方法也要返回一個 Promise 實例
let promise2;
// 因為 resolve 函數內邏輯是異步執(zhí)行的哆档,所以只有 then 方法被異步調用,才會進入這個分支住闯,在執(zhí)行 then 方法的時候瓜浸,resolve 操作已經完成,狀態(tài)已經變更
if (this.status === "fulfilled") {
return (promise2 = new Promise((resolve, reject) => {
// 返回的 promise2 中的代碼要異步執(zhí)行
setTimeout(() => {
try {
// 執(zhí)行 onfulfilled 函數比原,得到返回結果插佛。
let result = onfulfilled(this.resolveVal);
// 得到結果 result 可能是普通值,可能依然是 Promise 實例量窘,通過 resolvePromise 進行處理
// 并且在 resolvePromise 函數中進行 promise2 的 resolve 或者 reject 操作
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
// 與上面同理
if (this.status === "rejected") {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.rejectVal);
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
// 因為 resolve 函數內邏輯是異步執(zhí)行的雇寇,因此當 then 方法被同步調用的時候,resolve 內的邏輯還未執(zhí)行蚌铜,狀態(tài)依然是 pending
// 此時需要保存調用 then 方法時傳入的 onfulfilled 和 onrejected 函數锨侯,在 resolve 執(zhí)行時再取出執(zhí)行
if (this.status === "pending") {
return (promise2 = new Promise((resolve, reject) => {
// 因為同一個 Promise 實例可能有多個 then 方法,所以將所有 then 方法內的 onfulfilled 函數進行保存冬殃,需要時依次執(zhí)行
this.onFulfilledArray.push((value) => {
try {
// 執(zhí)行 onfulfilled 函數囚痴,得到返回結果。
let result = onfulfilled(value);
// 得到結果 result 可能是普通值审葬,可能依然是 Promise 實例渡讼,通過 resolvePromise 進行處理
// 并且在 resolvePromise 函數中進行 promise2 的 resolve 或者 reject 操作
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push((error) => {
try {
let result = onrejected(error);
resolvePromise(promise2, result, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
};
這應該是兩篇文章到現在最接近最終實現并且注釋最詳細的一次了骂束。不過依然不能改變它非常難以理解的事實,理解的關鍵在于弄清楚當 result 為 Promise 類型時成箫,和 promise2 的關系,以及當 result 為 Promise 類型時遞歸調用 resolvePromise 函數的目的旨枯。
沒有什么捷徑蹬昌,只能多看多寫多思考吧,寫出測試代碼攀隔,然后順著執(zhí)行順序去跟蹤代碼皂贩。
我想出了這個流程圖盡可能地幫助大家理解。
先看測試代碼:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("first then");
}, 2000);
});
promise
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(`second then`);
}, 2000);
});
})
.then((data) => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`last then`);
}, 2000);
});
})
.then((data) => {
console.log(data);
});
2秒后輸出 'first then' 再過2秒后輸出 'second then' 再過2秒后輸出 'last then'
回過頭來再看昆汹,其實 resolvePromise 方法的作用非常明確明刷,當 onfulfilled 函數返回的數據(result)為普通值的話,還是像場景1一樣直接 resolve promise2 處理即可满粗,但是如果當這個 result 為 Promise 類型時辈末,就要在 result.then.onfulfilled 中去遞歸調用 resolvePromise ,當再進去 resolvePromise 的時候映皆,此時新的 result 參數如果是普通值了挤聘,就 resolve promise2 ,并將結果作為參數返回即可捅彻。
到這如果你都可以理解组去,可以說是基本掌握了 Promise 的除靜態(tài)方法外的全部基礎內容。為了簡化代碼步淹,更容易理解从隆,一些容錯機制沒有添加,并不影響整體思路的學習缭裆。
靜態(tài)方法键闺,不說你也會
靜態(tài)方法其實非常簡單,不說你也應該會幼驶,但是我還是簡單說下吧[捂臉]艾杏。
關于 Promise 的靜態(tài)方法如 Promise.resolve、 Promise.reject盅藻、 Promise.all 等等购桑,就不再一一實現了,只選擇平時用的比較多的 Promise.all 實現以下氏淑。
- Promise.all 的實現
Promise.all(iterable) 方法返回一個 Promise 實例勃蜘,此實例在 iterable 參數內所有的 promise 都“完成(resolved)”或參數中不包含 promise 時回調完成(resolve);
如果參數中 promise 有一個失敿俨小(rejected)缭贡,此實例回調失斅谩(reject),失敗原因的是第一個失敗 promise 的結果阳惹。
看下具體使用谍失。
場景1:
const promise1 = new Promise((resolve, reject) => {
resolve("p1");
});
const promise2 = new Promise((resolve, reject) => {
resolve("p2");
});
Promise.all([promise1, promise2]).then((data) => {
console.log(data);
});
打印出 ["p1", "p2"]
場景2:
const promise1 = new Promise((resolve, reject) => {
resolve("p1");
});
const promise2 = new Promise((resolve, reject) => {
reject("p2 失敗");
});
Promise.all([promise1, promise2])
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
打印出 p2 失敗
對照著使用,來實現這個 Promise.all :
Promise.all = function (promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError("promiseArray should be array!");
}
// 在外層包裹一個 Promise 莹汤,如果內部有一個 Promise 執(zhí)行不成功快鱼,就執(zhí)行最外層 Promise 的 reject
return new Promise((resolve, reject) => {
try {
let resultArray = [];
for (let i = 0; i < promiseArray.length; i++) {
promiseArray[i].then((data) => {
// 記錄 resolve 的值
resultArray.push(data);
// 全部完成后 執(zhí)行外出 Promise 的 resolve 將存放每一個成功值得數組返回即可
if (resultArray.length === promiseArray.length) {
resolve(resultArray);
}
}, reject);
}
} catch (e) {
reject(e);
}
});
};
實現起來非常容易,其他幾個靜態(tài)方法思路也大同小異纲岭,有時間可以自行補充抹竹。
結束啦!
總結 Promise 的兩篇筆記終于結束啦止潮,實現 Promise 并不是目的窃判,況且也不是完全按照規(guī)范去實現的,目的是學習思路喇闸,深層次得理解原理袄琳,達到融會貫通,這樣以后不論是自己的代碼設計參考它的實現原理還是在 Promise 的使用上遇到問題仅偎,解決起來都會得心應手跨蟹。
這部分內容還是比較難理解的,整理筆記得時候也會有很多地方需要靜下心來梳理思路橘沥。剛接觸一頭霧水也再正常不過了窗轩,不要灰心,多看多寫多想座咆,每看一次都會有不同的收獲痢艺。