零、原文與資料
1. 手寫 Promise;
一、Promise/A+ 規範
1.Promise存在三個狀態:pending(等待態)、fulfilled(成功態)、rejected(失敗態);
2.pending爲初始態,並可以轉化爲fulfilled和rejected;
3.成功時,不可轉爲其他狀態,且必須有一個不可改變的值 (value);
4.失敗時,不可轉爲其他狀態,且必須有一個不可改變的原因 (reason);
5.new Promise(executor = (resolve, reject) => {resolve(value)}), resolve(value)將狀態置爲 fulfilled;
6.new Promise(executor = (resolve, reject) => {reject(reason)}), reject(reason)將狀態置爲 rejected;
7.如果 executor 執行異常也會 reject();
8.thenable: then(onFulfilled, onRejected?);
8.1 onFulfilled: status 爲 fulfilled,執行 onFulfilled, 傳入 value
8.2 onRejected: status 爲 rejected, 執行 onRejected
二、同步 Promise
同步 Promise 沒啥需要特別注意的地方,代碼如下:
// 1.Promise存在三個狀態:pending(等待態)、fulfilled(成功態)、rejected(失敗態) const STATUS_PENDING = 'pending' const STATUS_FULFILLED = 'fulfilled' const STATUS_REJECTED = 'rejected' class myPromise { constructor(executor) { // pending爲初始態,並可以轉化爲fulfilled和rejected this.status = STATUS_PENDING this.value = '' // 3 this.reason = '' // 4 let resolve = value => { // 5. if (this.status === STATUS_PENDING) { this.status = STATUS_FULFILLED this.value = value } } let reject = reason => { //6. if (this.status === STATUS_PENDING) { this.status = STATUS_REJECTED this.reason = reason } } // 7. try { executor(resolve, reject); } catch (err) { reject(err); } } // 8. then(onFulfilled = () => {}, onRejected = () => {}) { // 8.1 if (this.status === STATUS_FULFILLED) { onFulfilled(this.value) } // 8.2 if (this.status === STATUS_REJECTED) { onRejected(this.reason) } } } let ps = new myPromise(resolve => { console.log('before resolve') resolve(1) }) ps.then(res => { console.log(res) }) let pe = new myPromise((resolve, reject) => { console.log('before reject') reject('reject error') }) pe.then(res => { console.log(res) }, error => { console.log(error) })
在上面的兩個例子中,所有任務的執行都是同步的,可以與接下來的異步 promise 的執行順序對比下。
三、異步 Promise
先放下我們的運行實例:
let pa = new myPromise(resolve => { console.log('before resolve') setTimeout(()=>{ resolve(1) },1000) }) pa.then(res => { console.log(res) })
在這裏,我們的 executor 函數體中有異步的代碼塊,那麼在執行 executor(resolve, reject); 的時候,setTimeout中的任務就會被推入異步執行棧中,等待主線程中的宏任務(詳見 EventLoop in Js)全部計算完成再執行這個任務,因此,我們打斷點會發現, then() 的執行會早於 resolve(1) , 而在 then() 執行的時候,pa.status 依然是 pending,接下啦根據邏輯判斷 then 函數執行完成退出,然後執行異步任務,整個代碼執行完畢,故控制檯只會打印出 'before resolve'。
明白了這裏的執行順序,我們即可以進行完善,代碼如下:(標記 改 A)
const STATUE_PENDING = 'pending'; const STATUE_FULFILLED = 'fulfilled'; const STATUS_REJECTED = 'rejected'; class MyPromise { constructor(executor) { // pending 是初始態,並可以轉化成 fulfilled 和 rejected this.status = STATUE_PENDING; this.value = ''; // 3. this.reason = ''; // 4. // 改 A start // 存放成功的數組 this.onResolvedCallbacks = []; // 存放失敗的數組 this.onRejectedCallbacks = []; // 改 A end let resolve = value => { // 5. if (this.status === STATUE_PENDING) { this.status = STATUE_FULFILLED; this.value = value; // 改 A start // 成功之後的執行棧, this.onResolvedCallbacks.forEach(fn => fn()); // 改 A end } } let reject = reason => { // 6. if (this.status === STATUE_PENDING) { this.status = STATUS_REJECTED; this.reason = reason; // 改 A start // 成功之後的執行棧, this.onRejectedCallbacks.forEach(fn => fn()); // 改 A end } } // 7. try { executor(resolve, reject); } catch (error) { reject(err); } } // 8 then(onFulfilled = () => {}, onRejected = () => {}){ // 8.1 if (this.status === STATUE_FULFILLED) onFulfilled(this.value); // 8.2 if (this.status === STATUS_REJECTED) onRejected(this.reason); // 改 A start // 如果是處於異步任務的 if (this.status === STATUE_PENDING) { // 推入相應的執行棧 this.onResolvedCallbacks.push(() => onFulfilled(this.value)); this.onRejectedCallbacks.push(() => onRejected(this.reason)); } // 改 A end } }
爲什麼要分析下這邊的執行順序,一來複習下 EventLoop,二來對下面的鏈式調用的理解比較重要(這裏也是打斷點才發現的,推翻了一直以來對 Promise.then 的理解,之前一直認爲 executor 中的異步任務執行完了才真正的去執行 then 函數和裏面的onFulfilled/onRejected 函數)。
四、new Promise().then().then()... 鏈式調用
Promise 的鏈式調用和 jquery 的鏈式調用是不同的,在 jquery 或者一些其他的三方包中,我們在函數末尾加上 return this 即可實現,所以這裏無論鏈多少,都是在同一個對象上做文章。而 Promise 的每一次(鏈式)調用,其都會產生一個新的 Promise 對象, 並基於這個新的 Promise 調用 then 函數,雖然我們寫的時候是.then().then()...
。 首先我們來看實例:
let pc = new MyPromise((resolve, reject) => { console.log(0); setTimeout(() => { resolve(1); }, 3000); }) pc.then(res => { console.log(res); return new MyPromise(resolve => { console.log(2); setTimeout(() => { resolve(3) }, 3000); }) }).then(res => { console.log(res); })
注意下結構,我們在第一個 then 的參數函數中會有一個新的 Promise 返回。
然後是 MyPromise 類:
const STATUE_PENDING = 'pending'; const STATUE_FULFILLED = 'fulfilled'; const STATUS_REJECTED = 'rejected'; function resolvePromise(promise2, x, resolve, reject) { // 處理循環引用報錯 if (x === promise2) { // reject 報錯 return reject(new TypeError('chaining cycle detected for promise')); } // 記錄, 防止多次調用 let called; // x 是對象(不包括 null)或者函數 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+ 規定,聲明 then = x 的 then 方法 let then = x.then; // then 是 function,則默認是 promise if (typeof then === 'function') { // 就讓 then 執行, 第一個參數是 this, 後面是成功的回調和失敗的回調 then.call(x, y => { // 成功和失敗只能調用一個 if (called) return; called = true; // resolve 的結果依舊是promise 那就繼續解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能調用一個 if (called) return; called = true; // 失敗,停止繼續調用 reject(err); }) } else {// 不是的話直接 resolve 即可 resolve(x); } } catch (error) { // 出錯,即失敗 if (called) return; called = true; // 取 then 出錯了那就不繼續了 reject(error); } } else { resolve(x); } } class MyPromise { constructor(executor) { this.status = STATUE_PENDING; this.value = ''; this.reason = ''; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.status === STATUE_PENDING) { this.status = STATUE_FULFILLED; this.value = value; this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = reason => { if (this.status === STATUE_PENDING) { this.status = STATUS_REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (error) { reject(err); } } then(onFulfilled, onRejected){ // onFulfilled 不是函數, 則忽略,直接返回 value onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; // onRejected 不是函數, 則忽略,直接扔出錯誤 onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; }; let promise2 = new MyPromise((resolve, reject) => { if (this.status === STATUE_FULFILLED) { // 推一個異步任務 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error) } }, 0); } if (this.status === STATUS_REJECTED) { // 推一個異步任務 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error) } }, 0); } if (this.status === STATUE_PENDING) { // 推到執行棧中 this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error) } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error) } }, 0); }) } }); return promise2; } }
這裏要注意的是 then 函數的執行會產生一個新的 Promise, 第一個 then 函數的參數函數的執行也會產生一個新的 Promise。
五、其他:catch、resolve、reject、race和all
這裏除 catch 外其餘均是靜態方法:
1. catch(特殊的 then 方法):
catch(fn){ return this.then(null,fn) }
2.reslove(resolve 一個值)
MyPromise.resolve = val => new Promise(resolve=> resolve(val))
3.reject(reject 一個值)
MyPromise.reject = val => new Promise((resolve,reject)=> reject(val))
4.race Promise.race([p1, p2, p3])裏面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。
MyPromise.race = promises => { return new MyPromise((resolve, reject) => promises.forEach(pro => pro.then(resolve, reject)) ) }
5.all Promise.all可以將多個Promise實例包裝成一個新的Promise實例。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果數組,而失敗的時候則返回最先被reject失敗狀態的值。
MyPromise.all = function (promises) { return new Promise((resolve, reject) => { let index = 0; let result = []; if (promises.length === 0) { resolve(result); } else { function processValue(i, data) { result[i] = data; if (++index === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; i++) { //promises[i] 可能是普通值 Promise.resolve(promises[i]).then((data) => { processValue(i, data); }, (err) => { reject(err); return; }); } } }); }
六、一點感悟
Promise/A+ 規範還是值得每一個合格的前端開發去閱讀的。
得:再一次複習了 eventLoop,推翻了之前對 then 函數的執行時機的錯誤認識;
目標:完全理清楚鏈式調用的任務棧和執行順序以及 Promise.all 的原理;