1.前言
本文分析 Promise 特性的瞭解,完整實現了 Promise 所有功能。沒有參考原生 Promise 的寫法,自己根據思路一步一步完成以及描述,每個構建模組由:1、Promise 特性描述;2、實現特性的完整思路(分析一波) 3、專案程式碼;4、功能測試程式碼 幾個部分組成。大致用到的知識有: 1、變數私有化;2、訂閱釋出模式;3、eventloop 理解;4、Promise特性;5、class 特性;6、物件型別的判定... 算了不寫了
強行塞這麼多我也是夠拼的
2.Promise 特徵分析
- Promise 有三種狀態: pending(執行中)、 fulfilled(成功執行)、settled(異常捕獲);
- Promise 可以通過 new 關鍵字建立一個 未完成的 Promise;
- Promise 可以直接通過 Promise.resolve 建立一個成功完成的 Promise 物件;
- Promise 可以直接通過 Promise.reject 建立一個異常狀態的 Promise 物件;
- 通過 new 關鍵字建立的 Promise 方法裡如果出現錯誤,會被 Promise 的 reject 捕獲;
- Promise.resolve / Promise.reject 接收 thenable 物件和 Promise 物件的處理方式;
- 當沒有錯誤處理時的,全域性的 Promise 拒絕處理;
- 串聯 Promise 以及 Promise 鏈返回值;
- Promise.all Promise.race;
3.Promise 的實現
-
狀態碼私有化
開始之前討論一波 class 私有屬性的實現,個人想到的方案如下:
1.通過閉包,將變數存放在 construct 方法裡;弊端,所有的其他的物件方法必須在 construct 內定義(NO)。
2.通過在定義 Promise 的環境下定義一個 Map,根據當前物件索引去獲取相應的私有值;弊端,因為 Map 的 key 是強引用,當定義的 Promise 不用時也不會被記憶體回收(NO);
3.通過在定義 Promise 的環境下定義一個 WeakMap,根據當前物件索引去獲取相應的私有值; 優勢,木有以上兩種劣勢(不寫點什麼感覺難受);
說了這麼多那麼我們們要用第三種方法嗎?NO,原生 [[PromiseState]] 是一個內部屬性,不暴露在 Promise 上,但是通過瀏覽器的控制檯可以看到,用第三種方式模仿並不能直觀的在控制檯看到,所以我決定還是不要作為私有變數出現,但是把列舉特性幹掉了 假裝他是私有變數
心裡好過一點因此你就能看到下面的程式碼;
const PENDDING = 'pendding';// 等待狀態
const FULFILLED = 'resolved';// 成功操作狀態
const REJECTED = 'rejected';// 捕獲錯誤狀態
class MyPromise{
constructor(handler){
// 資料初始化
this.init();
}
// 資料初始化
init(){
Object.defineProperties(this,{
'[[PromiseState]]': {
value: PENDDING,
writable: true,
enumerable: false
},
'[[PromiseValue]]': {
value: undefined,
writable: true,
enumerable: false
},
'thenQueue':{
value: [],
writable: true,
enumerable: false
},
'catchQueue':{
value: [],
writable: true,
enumerable: false
}
})
}
// 獲取當前狀態
getPromiseState (){
return this['[[PromiseState]]'];
}
// 設定當前狀態
setPromiseState (state) {
Object.defineProperty(this, '[[PromiseState]]', {
value: state,
writable: false
})
}
// 獲取當前值
getPromiseValue (){
return this['[[PromiseValue]]'];
}
// 設定當前值
setPromiseValue (val) {
Object.defineProperty(this, '[[PromiseValue]]', {
value: val
})
}
}
複製程式碼
-
建立一個未完成狀態的Promise
函式呼叫過程分析:
- 使用者通過 new 關鍵字傳入一個方法;
- 方法有兩個引數
resolve
和reject
兩個方法 - 當傳入的方法呼叫
resolve
時,狀態變為 fulfilled,有且只有接收一次resolve
裡的方法裡的值作為[[PromiseValue]]
,供該 Promise 物件下的then
方法使用; - 當傳入的方法呼叫
reject
時,狀態變為 rejected,有且只有接收一次reject
裡的方法裡的值作為[[PromiseValue]]
,供該 Promise 物件下的catch
方法使用;
程式碼思路:
-
首先傳入的函式應該在 construct 方法裡進行呼叫;
-
因具備一個存放待執行成功操作方法的佇列,一個存放捕獲異常方法的佇列。
-
resolve
方法下處理的問題是:1、判斷當前狀態是否是等待狀態,如果不是則啥也不幹,如果是走第二步
2、修改
[[PromiseState]]
為FULFILLED;3、將
[[PromiseValue]]
賦值為方法傳遞進來的引數;4、成功操作方法的佇列在 eventloop 結束後①依次呼叫然後清空,捕獲異常方法的佇列清空;
-
reject
方法基本就不贅述啦...... -
then
方法:1、 判斷當前狀態是否為等待,是等待進行第 2 步,否則進行第 3 步;
2、 加入成功操作方法佇列;
3、 當前eventloop 結束非同步呼叫;
-
catch
方法不贅述
ps: 注①因為無法將任務插入 microtask 中,就用 eventloop結束作為替代;
// 事件迴圈最後執行
const eventLoopEndRun = function (handler){
setImmediate(()=>{
handler()
})
}
// ...
class MyPromise{
constructor(handler){
// ...
// 方法傳遞,通過 bind 保持兩個方法對當前物件的引用
handler(this.resolve.bind(this), this.reject.bind(this));
}
// ...
// 清空等待佇列
clearQueue (currentState) {
const doQueue = currentState === REJECTED ? this.catchQueue : this.thenQueue;
const promiseData = this.getPromiseValue();
doQueue.forEach(queueHandler=>queueHandler(promiseData));
this.catchQueue = [];
this.thenQueue = []
}
// 狀態改變方法
changeStateHandler (currentState, data){
this.setPromiseState(currentState);
this.setPromiseValue(data);
setImmediate(()=>{this.clearQueue(currentState)});
// 保持狀態只能改變一次
this.changeStateHandler = null;
this.setPromiseState = null;
this.setPromiseValue = null;
}
// 不解釋
resolve (data) {
this.changeStateHandler && this.changeStateHandler(FULFILLED, data);
}
// 不解釋
reject (err) {
this.changeStateHandler && this.changeStateHandler(REJECTED, err);
}
// 不解釋
then(thenHandler){
const currentState = this.getPromiseState();
const promiseData = this.getPromiseValue();
if (currentState === FULFILLED) thenHandler(promiseData);
else if (currentState === PENDDING) this.thenQueue.push(thenHandler);
}
// 不解釋
catch(catchHandler){
const currentState = this.getPromiseState();
const promiseData = this.getPromiseValue();
if (currentState === REJECTED) catchHandler(promiseData);
else if (currentState === PENDDING) this.catchQueue.push(catchHandler);
}
}
// 測試方法
const test1 = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('2s 後輸出了我');
}, 2000)
});
const test2 = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
reject('我出錯啦!')
}, 2000)
})
test1.then(data=>console.log(data));
test1.catch(err=>console.log(err));
test2.then(data=>console.log(data));
test2.catch(err=>console.log(err));
console.log("我是最早的");
複製程式碼
-
建立一個完成狀態的Promise
通過 Promise.resolve() 建立一個成功操作的 Promise 物件; Promise.reject() 建立一個捕獲錯誤的 Promise 物件,new 關鍵字傳入的方法體有報錯,會直接被 reject 捕獲;
分析一波:
-
能直接呼叫的方法,妥妥應該的是一個靜態方法;
-
呼叫之後要生成一個新的 Promise 物件;
-
所以我們們就要分兩步走 1,建立一個 Promise 物件,然後呼叫其 resolve 方法.
-
因為例項化的物件不能獲取寄幾的 static 方法
-
通過 try+catch 捕獲 handler 異常,並通過 reject 進行丟擲;
-
// ...
// construct 方法新增一個型別,當 new 關鍵字進來傳遞的不是一個函式,我們們同樣在 eventLoop 結束丟擲一個錯誤
if(Object.prototype.toString.call(handler) !== "[object Function]"){
eventLoopEndRun(()=>{
throw new Error(`MyPromise resolver ${typeof handler} is not a function`)
})
} else {
// 方法傳遞,this指向會變,通過 bind 保持兩個方法對當前物件的引用
// 當然也可以這麼玩:data=>this.resolve(data)
try{
handler(this.resolve.bind(this), this.reject.bind(this));
} catch(err) {
this.reject(err);
}
}
// ...
// 不解釋
static resolve (data) {
return new MyPromise(resolve=>resolve(data));
}
// 不解釋
static reject (err) {
return new MyPromise((resolve, reject)=>{reject(err)});
}
// 測試方法
var resolvePromise = MyPromise.resolve(111);
resolvePromise.then(data=>console.log(data));
var rejectPromise = MyPromise.reject('這個錯了');
rejectPromise.catch(data=>console.log(data));
new MyPromise();
var errPromise = new MyPromise(()=>{throw new Error("我錯了")});
errPromise.catch(data=>console.log(data.message));
複製程式碼
-
thenable 物件 + 全域性錯誤監聽
thenable 物件是啥?就是有個屬性為 then 方法的物件,then 方法裡有兩個引數,resolve、reject 至於 resolve 和 reject 的作用,就不贅述啦
好像還是打了很多字。全域性錯誤監聽,監聽分為兩種(書上的說法是): 一個觸發是當前事件迴圈結束前沒有catch 當前錯誤 Promise --- unhandledRejection;一個觸發是當前事件迴圈後,當 Promise 被拒絕,並且沒有 catch 程式,就會被觸發 --- rejectionHandled。經過 node 環境下測試(在 Chrome 控制檯測試好像無論如何都不會被觸發)感覺是 rejectionHandled 觸發實在新的時間迴圈新增 catch 程式後才會被觸發,大致流程圖如下。
let rejected; process.on('unhandledRejection',function(event){ console.log('onunhandledrejection'); }) process.on('rejectionHandled',function(event){ console.log('onrejectionhandled'); }) rejected = Promise.reject(new Error('xx')) eventLoopEndRun(()=>{ console.log(123); rejected.catch(err=>{ console.log(err.message) }) rejected.catch(err=>{ console.log(err.message) }) }) 複製程式碼
分析一波:
-
在 reject 階段進行訂閱
unhanlderReject
事件; -
catch 函式中移除當前 Promise 對
unhandledRejection
事件的訂閱,執行傳入 catch 前釋出當前 Promise 的rejectionHandled
事件。 -
當前事件迴圈結束,我們需要優先對
unhanlderReject
事件進行釋出,所以我們需要調整eventLoopEndRun 函式;當Promise沒有 catch 程式,且沒有全域性沒有unhanlderReject
監聽,我們就要丟擲相應的錯誤。 -
我們需要自定義這個 訂閱釋出者,然後能通過當前 Promise 使得事件觸發繫結相應的回撥。
-
這個釋出訂閱者具有備的功能有: 1、新增監聽回撥;2、訂閱和取消訂閱;3、相應的事件釋出後,將對應 map 中 Promise 修改狀態。
-
於是乎程式碼如下:
// PromiseSubscribePublish.js
const UNHANDLEDREJECTION = 'UNHANDLEDREJECTION'; // 當前事件迴圈,無 catch 函式狀態;
const REJECTIONHANDLED = 'REJECTIONHANDLED'; // 事件迴圈後,無 catch 函式狀態;
class PromiseSubscribePublish{
constructor(){
this.subscribeUnhandler = new Map();
this.subscribeHandler = new Map();
this.errFuc = {}
}
// 監聽事件繫結
bindLisener (type, cb){
console.log(type.toUpperCase(), UNHANDLEDREJECTION)
if(type.toUpperCase() !== UNHANDLEDREJECTION && type.toUpperCase() !== REJECTIONHANDLED) throw Error('type toUpperCase must be UNHANDLEDREJECTION or REJECTIONHANDLED');
if(Object.prototype.toString.call(cb) !== "[object Function]") throw Error('callback is not function');
this.errFuc[type.toUpperCase()] = cb;
}
subscribe(promise, err){
// 訂閱一波,以當前 Promise 為 key,err 為引數,加入 unhandler map 中
this.subscribeUnhandler.set(promise, err)
}
quitSubscribe(promise){
this.subscribeUnhandler.delete(promise);
}
publish (type, promise) {
let changgeStateFuc; // 定義當前狀態變換操作
const errFuc = this.errFuc[type]; // 當前繫結的監聽函式
if(type === UNHANDLEDREJECTION){
// 沒有訂閱事件的 promise 則啥也不幹
if (!this.subscribeUnhandler.size) return;
// 根據當前事件型別,選擇處理函式
changgeStateFuc = (err, promise)=>{
this.subscribeHandler.set(promise);
this.subscribeUnhandler.delete(promise, err);
}
// 不論如何當前時間迴圈下的等待佇列狀態全部需要變更
if(errFuc){
this.subscribeUnhandler.forEach((err, promise)=>{
errFuc(err, promise)
changgeStateFuc(err, promise)
})
} else {
this.subscribeUnhandler.forEach((err, promise)=>{
changgeStateFuc(err, promise)
})
console.error('Uncaught (in promise)', err);
}
} else {
// 如果該 promise 沒有進行訂閱
if(!this.subscribeHandler.has(promise)) return;
// 哪個 promise 釋出 catch 函式,就根據當前 Promise 執行相應方法,並將其從 Handler 訂閱者裡刪除
errFuc && errFuc(promise);
this.subscribeHandler.delete(promise);
}
}
}
// 定義一些靜態成員變數 預設不可寫
Object.defineProperties(PromiseSubscribePublish, {
[UNHANDLEDREJECTION]:{
value: UNHANDLEDREJECTION
},
[REJECTIONHANDLED]:{
value: REJECTIONHANDLED
}
})
module.exports = PromiseSubscribePublish;
// MyPromise.js
// ..
const PromiseSubscribePublish = require('./PromiseSubscribePublish');
const promiseSubscribePublish = new PromiseSubscribePublish();
// 事件迴圈最後執行
const eventLoopEndRun = (()=>{
let unhandledPub;
let timer;
const queueHandler = [];
// 啟用事件迴圈最後執行
const activateRun = ()=>{
// 截流
timer && clearTimeout(timer);
timer = setTimeout(()=>{
unhandledPub && unhandledPub();
let handler = queueHandler.shift();
while(handler){
handler();
handler = queueHandler.shift();
}
},0);
}
// 設定 unhanldedReject 優先順序最高 , 直接加入佇列
return (handler,immediate)=> {
immediate ? unhandledPub = handler : queueHandler.push(handler);
activateRun();
}
})()
//...
reject (err) {
this.changeStateHandler && this.changeStateHandler(REJECTED, err);
promiseSubscribePublish.subscribe(this, err);
// 存在 reject ,事件迴圈結束髮布 UNHANDLEDREJECTION
eventLoopEndRun(()=>
promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
true
);
}
//...
static unhandledRejectionLisener(cb){
promiseSubscribePublish.bindLisener(PromiseSubscribePublish.UNHANDLEDREJECTION ,cb)
}
static rejectionHandledLisener(cb){
promiseSubscribePublish.bindLisener(PromiseSubscribePublish.REJECTIONHANDLED ,cb)
}
// ...
catch(catchHandler){
const currentState = this.getPromiseState();
const promiseData = this.getPromiseValue();
// 取消當前事件迴圈下 reject 狀態未 catch 事件訂閱;
promiseSubscribePublish.quitSubscribe(this);
if (currentState === REJECTED) {
eventLoopEndRun(()=>{
// 釋出 catch 處理
promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this);
catchHandler(promiseData);
});
}
else if (currentState === PENDDING) this.catchQueue.push(catchHandler);
}
// 測試程式碼
MyPromise.unhandledRejectionLisener((err,promise)=>{
console.log(err, promise);
})
MyPromise.rejectionHandledLisener((err,promise)=>{
console.log(err, promise);
})
var myPromise = MyPromise.reject(11);
// myPromise.catch(()=>{console.log('catch')});
setTimeout(()=>{
myPromise.catch(()=>{console.log('catch')});
},1000)
複製程式碼
-
串聯 Promise 以及 Promise 鏈返回值
看到鏈式,首先想到的是 jquery 呼叫。jquery 返回的是 jquery 物件本體。而 Promise 根據狀態判斷:
- 當是操作成功狀態時,呼叫 catch 會返回和當前 Promise 的
[[PromiseStatus]]
和[[PromiseValues]]
狀態相同新構建的 Promise;呼叫 then 方法時,返回和當前 Promise 的[[PromiseStatus]]
相同的,[[PromiseValues]]
值為 then 方法返回值的 新構建的 Promise; - 當是捕獲錯誤狀態時,呼叫 then 會返回和當前 Promise 的
[[PromiseStatus]]
和[[PromiseValues]]
狀態相同新構建的 Promise;呼叫 catch 方法時, 返回操作成功的新構建的 Promise ,[[PromiseValues]]
值為 catch 方法返回值; - 當執行 catch 或 then 方法體內有報錯,直接返回一個新構建捕獲錯誤的 Promise ,
[[PromiseValues]]
為那個錯誤; - 如果 Promise 中有一環出現錯誤,而鏈中沒有 catch 方法,則丟擲錯誤,否則把鏈上的所有 Promise 都從
unhandledRejuect
訂閱中去除。 - 因為 then 和 catch 回撥方法是當前事件迴圈結束時才執行,而 catch 去除 Promise 鏈上
unhandledRejuect
訂閱是當前事件迴圈,如果鏈上有方法報錯,unhandledRejuect
訂閱會再次發生,這樣會造成哪怕當前報錯 Promise 後有 catch,也會丟擲錯誤,因此需要給當前 Promise 加一個屬性,以標誌鏈後有 catch,使得其不訂閱unhandledRejuect
事件。
分析一波:
- 要在例項方法中,建立另一個當前類的例項時,必須用到當前類的建構函式。當我們們的類被繼承出一個派生類,我們們希望返回的是那個派生類,於是不能直接 new MyPromise 去建立,而要使用一個 Symbol.species
- 新建 Promise 和之前的 Promise 存在關聯,所以當前 Promise 的狀態決定新 Promise 狀態,構建新 Promise 的過程中當前 Promise 的捕獲函式不能將其訂閱從 unhandledReject 中移除,所以需要一個標誌位來標識 then 函式屬性。
- Promise 鏈上如果出現 catch 函式,鏈上 catch 函式之前的所有 Promise 都將從訂閱 unhandledReject Map 中移除,因此 Promise 需要記錄鏈上的上一級 Promise;
- Promise then 或 catch 方法體內報錯將構建一個捕獲錯誤狀態的 Promise,因此需要一個函式去捕獲可能發生的錯誤;
- 當是操作成功狀態時,呼叫 catch 會返回和當前 Promise 的
//... MyPromise.js
const runFucMaybeError = handler => {
try {
return handler();
} catch(err) {
return {
iserror: FUCERROR,
err
};
}
}
const clearLinksSubscribe = linkPrePromise=>{
while(linkPrePromise && !linkPrePromise.hascatch){
linkPrePromise.hascatch = true;
promiseSubscribePublish.quitSubscribe(linkPrePromise);
linkPrePromise = linkPrePromise.linkPrePromise;
}
}
// 不解釋
then(thenHandler, quitReturn){
const currentState = this.getPromiseState();
const promiseData = this.getPromiseValue();
let nextPromiseData;
if (currentState === FULFILLED) eventLoopEndRun(()=>{
nextPromiseData = runFucMaybeError(()=>thenHandler(promiseData))
});
else if (currentState === PENDDING) this.thenQueue.push(data=>{
nextPromiseData = runFucMaybeError(()=>thenHandler(data))
});
if(!quitReturn){
const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{
this.catch(err=>{
reject(err);
}, true);
// 根據佇列原則,執行肯定在當前 then 後,保證能正確拿到前一個 Promise 的返回值
this.then(()=>{
nextPromiseData && nextPromiseData.iserror === FUCERROR
? reject(nextPromiseData.err)
: resolve(nextPromiseData)
}, true)
})
nextPromise.linkPrePromise = this;
return nextPromise;
};
}
catch(catchHandler, quitReturn){
const currentState = this.getPromiseState();
const promiseData = this.getPromiseValue();
let nextPromiseData;
// 取消當前事件迴圈下 reject 狀態未 catch 事件訂閱;
// 當是例項內部呼叫時,不能將當前 Promise 從 unhandledReject 佇列中移除;
// 否則順著生成鏈依次將 Promise 移除;
if(!quitReturn)clearLinksSubscribe(this)
if (currentState === REJECTED) {
eventLoopEndRun(()=>{
// 釋出 catch 處理
promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this);
nextPromiseData = runFucMaybeError(()=>catchHandler(promiseData));
});
}
else if (currentState === PENDDING) this.catchQueue.push(data=>{
nextPromiseData = runFucMaybeError(()=>{catchHandler(data)})
});
if(!quitReturn){
const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{
// 根據佇列原則,執行肯定在當前 then 後,保證能正確拿到報錯的 Promise 的返回值
this.catch(()=>{
nextPromiseData && nextPromiseData.iserror === FUCERROR
? reject(nextPromiseData.err)
: resolve(nextPromiseData)
}, true);
this.then(data=>resolve(data), true)
})
nextPromise.linkPrePromise = this;
return nextPromise;
}
}
// 測試程式碼
const test1 = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('2s 後輸出了我');
}, 2000)
});
test1.then(data=>{
console.log(data);
return '你好'
}).then(data=>{
console.log(data);
return '不好'
}).then(data=>{
console.log(data);
});
test1.catch(err=>console.log(err)).then(data=>{
console.log(data);
return 'gggg'
}).then(data=>{
console.log(data);
});
const test2 = new MyPromise((resolve,reject)=>{
throw new Error('xx');
})
test2.then(data=>console.log(data)).catch(err=>console.log(err));
test2.catch(err=>console.log(err)).then(data=>{
console.log(data);
return '你好'
}).then(data=>{
console.log(data);
return '不好'
}).then(data=>{
console.log(data);
});
var a = MyPromise.resolve(1);
var b = a.then(data=>{throw new Error('11')}).catch(err=>{console.log(err.message)})
複製程式碼
-
Promise.all + Promise.race;
Promise.all 有如下特性: 1、接收一個具有[Symbol.iterator]函式的資料, 返回一個 Promise,該 Promise 成功操作,then 方法傳入一個陣列,陣列資料位置和迭代器迭代返回的順序相關聯,該 Promise 捕獲錯誤 catch 裡的傳入捕獲的錯誤; 2、 迭代器遍歷結果如果是 Promise , 則將其 PromiseValue 作為值,插入傳入陣列對應的位置,當遍歷結果不是 Promise 直接插入陣列對應位置,當遇到捕獲錯誤,或者 Promise 出現錯誤時直接將狀態轉變為 rejected 狀態 ,從 catch 拿到相應錯誤的值;總結就是有錯馬上拋,要不等所有資料處理完才改變狀態;
Promise.race 就不贅述:記住幾點,傳入引數要求和 .all 相同,資料處理方式是,先到先得,率先處理完的資料直接修改狀態。
在分析一波之前,調整幾個之前的沒有考慮到的問題:
- 將狀態改變函式覆蓋操作移至 resolve 和 reject 函式中。
- reject 方法體執行全都由是否能改變狀態決定。
- reject 新增一個引數,表示不訂閱
unhandledReject
事件,因為 then 方法也會生成新的 Promise,而 then 鏈前有捕獲異常狀態的 Promise 會造成重複報錯,catch 無所謂,因為本身會Promise 鏈佇列。
// 開頭的 '-' 標示移除,'+' 表示新增
// ... changeStateHandler 方法
- this.changeStateHandler = null;
resolve (data) {
if(this.changeStateHandler){
this.changeStateHandler(FULFILLED, data);
// 保持狀態只能改變一次
this.changeStateHandler = null;
}
}
reject (err, noSubscribe) {
if(this.changeStateHandler){
this.changeStateHandler(REJECTED, err);
!noSubscribe && !this.hascatch && promiseSubscribePublish.subscribe(this, err);
// 存在 reject ,事件迴圈結束髮布 UNHANDLEDREJECTION
eventLoopEndRun(()=>
promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
true
);
// 保持狀態只能改變一次
this.changeStateHandler = null;
}
}
// then 方法
- this.catch(err=>{
reject(err)
}, true);
+ this.catch(err=>reject(err, true), true);
複製程式碼
接下來開始分析一波:
-
首先我們們的判斷,傳入的是否具有
Symbol.iterator
,沒有就直接拋錯(Promise 狀態會直接變為 reject,就不往下說了); -
因為我們們定義的 MyPromise 所以判斷型別應該是 MyPromise,如果想要通過
Object.prototype.toString.call
去判斷,我們們需要給我們們的類加一個 tag -
.all 處理完一波資料插入結果值對應的位置,判斷是否資料完全處理完,如果全部處理完才改變狀態。.race 處理完那個直接改變狀態,忽略後面、忽略後面、忽略後面(重要的事情嗶嗶3次)。
-
兩邊如果有傳入的 Promise 狀態出現捕獲異常,返回的 Promise 狀態即變為異常,catch 得到的值即為傳入 Promise 異常的那個異常
繞死你。 -
因為是靜態方法所以不能用 Symbol.species 構建例項。
// MyPromise.js 最後頭
MyPromise.prototype[Symbol.toStringTag] = "MyPromise";
static all (promiseArr){
// 因為是靜態方法 無法獲取 this 所以不能使用例項內部方法構建方式去構建新物件
return new MyPromise((resolve,reject)=>{
const iterator = isIterator(promiseArr);
if(typeof iterator === 'string'){
console.error(iterator);
throw new Error(iterator);
}
let data = iterator.next();
const result = [];
let index = -1; // Promise 應存放返回陣列的位置;
let waitPromiseNum = 0; // 統計未完成的 Promise;
let checkAllEnd = () => {
return waitPromiseNum === 0;
}
while (data) {
if(data.done) break;
index ++;
if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){
result[index] = data.value;
} else {
(index=>{
const promise = data.value;
waitPromiseNum++;
promise.then(data=>{
result[index] = data;
waitPromiseNum--;
// 看是否 Promise 全部完成
if(checkAllEnd())resolve(result);
}).catch(data=>reject(data));
})(index)
}
data = iterator.next();
}
if(checkAllEnd())resolve(result);
})
}
static race (promiseArr){
// 因為是靜態方法 無法獲取 this 所以不能使用例項內部方法構建方式去構建新物件
return new MyPromise((resolve,reject)=>{
const iterator = isIterator(promiseArr);
if(typeof iterator === 'string'){
console.error(iterator);
throw new Error(iterator);
}
let data = iterator.next();
while (data) {
if(data.done) break;
if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){
return resolve(data.value);
} else {
data.value
.then(data=>resolve(data))
.catch(data=>reject(data));
}
data = iterator.next();
}
})
}
// 測試方法
MyPromise.all(
[
MyPromise.resolve(1),
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.resolve(3)
]).then(data=>{console.log(data)});
MyPromise.all([
1,
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.resolve(3)
]).then(data=>{console.log(data)});
MyPromise.all([
MyPromise.resolve(1),
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.reject(3)
]).then(data=>{console.log(data)});
MyPromise.race([
MyPromise.resolve(1),
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.resolve(3)
]).then(data=>{console.log(data)});
MyPromise.race([
1,
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.resolve(3)
]).then(data=>{console.log(data)});
MyPromise.race([
MyPromise.resolve(1),
new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
MyPromise.reject(3)
]).then(data=>{console.log(data)});
複製程式碼
結束
如果發現過程遇到什麼問題,歡迎及時提出。