1.前言
本文分析 Promise 特性的了解数初,完整實現(xiàn)了 Promise 所有功能次酌。沒有參考原生 Promise 的寫法秀存,自己根據(jù)思路一步一步完成以及描述羔杨,每個構建模塊由:1性誉、Promise 特性描述窿吩;2、實現(xiàn)特性的完整思路(分析一波) 3错览、項目代碼纫雁;4、功能測試代碼 幾個部分組成倾哺。大致用到的知識有: 1轧邪、變量私有化;2羞海、訂閱發(fā)布模式忌愚;3、eventloop 理解却邓;4硕糊、Promise特性;5腊徙、class 特性简十;6、對象類型的判定... 算了不寫了
強行塞這么多我也是夠拼的
2.Promise 特征分析
- Promise 有三種狀態(tài): pending(執(zhí)行中)螟蝙、 fulfilled(成功執(zhí)行)、settled(異常捕獲);
- Promise 可以通過 new 關鍵字創(chuàng)建一個 未完成的 Promise;
- Promise 可以直接通過 Promise.resolve 創(chuàng)建一個成功完成的 Promise 對象;
- Promise 可以直接通過 Promise.reject 創(chuàng)建一個異常狀態(tài)的 Promise 對象;
- 通過 new 關鍵字創(chuàng)建的 Promise 方法里如果出現(xiàn)錯誤民傻,會被 Promise 的 reject 捕獲;
- Promise.resolve / Promise.reject 接收 thenable 對象和 Promise 對象的處理方式;
- 當沒有錯誤處理時的胰默,全局的 Promise 拒絕處理;
- 串聯(lián) Promise 以及 Promise 鏈返回值;
- Promise.all Promise.race;
3.Promise 的實現(xiàn)
-
狀態(tài)碼私有化
開始之前討論一波 class 私有屬性的實現(xiàn)场斑,個人想到的方案如下:
1.通過閉包,將變量存放在 construct 方法里;弊端初坠,所有的其他的對象方法必須在 construct 內定義(NO)和簸。
2.通過在定義 Promise 的環(huán)境下定義一個 Map彭雾,根據(jù)當前對象索引去獲取相應的私有值碟刺;弊端,因為 Map 的 key 是強引用薯酝,當定義的 Promise 不用時也不會被內存回收(NO)半沽;
3.通過在定義 Promise 的環(huán)境下定義一個 WeakMap,根據(jù)當前對象索引去獲取相應的私有值吴菠; 優(yōu)勢者填,木有以上兩種劣勢(不寫點什么感覺難受);
說了這么多那么咱們要用第三種方法嗎做葵?NO占哟,原生 [[PromiseState]] 是一個內部屬性,不暴露在 Promise 上酿矢,但是通過瀏覽器的控制臺可以看到榨乎,用第三種方式模仿并不能直觀的在控制臺看到,所以我決定還是不要作為私有變量出現(xiàn)瘫筐,但是把枚舉特性干掉了 假裝他是私有變量
心里好過一點因此你就能看到下面的代碼;
const PENDDING = 'pendding';// 等待狀態(tài)
const FULFILLED = 'resolved';// 成功操作狀態(tài)
const REJECTED = 'rejected';// 捕獲錯誤狀態(tài)
class MyPromise{
constructor(handler){
// 數(shù)據(jù)初始化
this.init();
}
// 數(shù)據(jù)初始化
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
}
})
}
// 獲取當前狀態(tài)
getPromiseState (){
return this['[[PromiseState]]'];
}
// 設置當前狀態(tài)
setPromiseState (state) {
Object.defineProperty(this, '[[PromiseState]]', {
value: state,
writable: false
})
}
// 獲取當前值
getPromiseValue (){
return this['[[PromiseValue]]'];
}
// 設置當前值
setPromiseValue (val) {
Object.defineProperty(this, '[[PromiseValue]]', {
value: val
})
}
}
-
創(chuàng)建一個未完成狀態(tài)的Promise
函數(shù)調用過程分析:
- 使用者通過 new 關鍵字傳入一個方法蜜暑;
- 方法有兩個參數(shù)
resolve
和reject
兩個方法 - 當傳入的方法調用
resolve
時,狀態(tài)變?yōu)?fulfilled策肝,有且只有接收一次resolve
里的方法里的值作為[[PromiseValue]]
肛捍,供該 Promise 對象下的then
方法使用; - 當傳入的方法調用
reject
時之众,狀態(tài)變?yōu)?rejected拙毫,有且只有接收一次reject
里的方法里的值作為[[PromiseValue]]
,供該 Promise 對象下的catch
方法使用棺禾;
代碼思路:
首先傳入的函數(shù)應該在 construct 方法里進行調用缀蹄;
因具備一個存放待執(zhí)行成功操作方法的隊列,一個存放捕獲異常方法的隊列帘睦。
-
resolve
方法下處理的問題是:1袍患、判斷當前狀態(tài)是否是等待狀態(tài),如果不是則啥也不干竣付,如果是走第二步
2诡延、修改
[[PromiseState]]
為FULFILLED;3、將
[[PromiseValue]]
賦值為方法傳遞進來的參數(shù)古胆;4肆良、成功操作方法的隊列在 eventloop 結束后①依次調用然后清空筛璧,捕獲異常方法的隊列清空;
reject
方法基本就不贅述啦......-
then
方法:1惹恃、 判斷當前狀態(tài)是否為等待夭谤,是等待進行第 2 步,否則進行第 3 步巫糙;
2朗儒、 加入成功操作方法隊列;
3参淹、 當前eventloop 結束異步調用醉锄;
catch
方法不贅述
ps: 注①因為無法將任務插入 microtask 中,就用 eventloop結束作為替代浙值;
// 事件循環(huán)最后執(zhí)行
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 = []
}
// 狀態(tài)改變方法
changeStateHandler (currentState, data){
this.setPromiseState(currentState);
this.setPromiseValue(data);
setImmediate(()=>{this.clearQueue(currentState)});
// 保持狀態(tài)只能改變一次
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("我是最早的");
-
創(chuàng)建一個完成狀態(tài)的Promise
通過 Promise.resolve() 創(chuàng)建一個成功操作的 Promise 對象开呐; Promise.reject() 創(chuàng)建一個捕獲錯誤的 Promise 對象烟勋,new 關鍵字傳入的方法體有報錯,會直接被 reject 捕獲筐付;
分析一波:
能直接調用的方法卵惦,妥妥應該的是一個靜態(tài)方法;
調用之后要生成一個新的 Promise 對象家妆;
所以咱們就要分兩步走 1鸵荠,創(chuàng)建一個 Promise 對象,然后調用其 resolve 方法.
因為實例化的對象不能獲取寄幾的 static 方法
通過 try+catch 捕獲 handler 異常伤极,并通過 reject 進行拋出蛹找;
// ...
// construct 方法新增一個類型,當 new 關鍵字進來傳遞的不是一個函數(shù)哨坪,咱們同樣在 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 對象 + 全局錯誤監(jiān)聽
thenable 對象是啥当编?就是有個屬性為 then 方法的對象届慈,then 方法里有兩個參數(shù),resolve忿偷、reject 至于 resolve 和 reject 的作用金顿,就不贅述啦
好像還是打了很多字。全局錯誤監(jiān)聽鲤桥,監(jiān)聽分為兩種(書上的說法是): 一個觸發(fā)是當前事件循環(huán)結束前沒有catch 當前錯誤 Promise --- unhandledRejection揍拆;一個觸發(fā)是當前事件循環(huán)后,當 Promise 被拒絕茶凳,并且沒有 catch 程序嫂拴,就會被觸發(fā) --- rejectionHandled播揪。經過 node 環(huán)境下測試(在 Chrome 控制臺測試好像無論如何都不會被觸發(fā))感覺是 rejectionHandled 觸發(fā)實在新的時間循環(huán)添加 catch 程序后才會被觸發(fā),大致流程圖如下筒狠。
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 函數(shù)中移除當前 Promise 對
unhandledRejection
事件的訂閱,執(zhí)行傳入 catch 前發(fā)布當前 Promise 的rejectionHandled
事件辩恼。當前事件循環(huán)結束雇庙,我們需要優(yōu)先對
unhanlderReject
事件進行發(fā)布,所以我們需要調整eventLoopEndRun 函數(shù)运挫;當Promise沒有 catch 程序,且沒有全局沒有unhanlderReject
監(jiān)聽状共,我們就要拋出相應的錯誤套耕。我們需要自定義這個 訂閱發(fā)布者谁帕,然后能通過當前 Promise 使得事件觸發(fā)綁定相應的回調。
這個發(fā)布訂閱者具有備的功能有: 1冯袍、新增監(jiān)聽回調匈挖;2、訂閱和取消訂閱康愤;3儡循、相應的事件發(fā)布后,將對應 map 中 Promise 修改狀態(tài)征冷。
于是乎代碼如下:
// PromiseSubscribePublish.js
const UNHANDLEDREJECTION = 'UNHANDLEDREJECTION'; // 當前事件循環(huán)择膝,無 catch 函數(shù)狀態(tài);
const REJECTIONHANDLED = 'REJECTIONHANDLED'; // 事件循環(huán)后检激,無 catch 函數(shù)狀態(tài)肴捉;
class PromiseSubscribePublish{
constructor(){
this.subscribeUnhandler = new Map();
this.subscribeHandler = new Map();
this.errFuc = {}
}
// 監(jiān)聽事件綁定
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 為參數(shù),加入 unhandler map 中
this.subscribeUnhandler.set(promise, err)
}
quitSubscribe(promise){
this.subscribeUnhandler.delete(promise);
}
publish (type, promise) {
let changgeStateFuc; // 定義當前狀態(tài)變換操作
const errFuc = this.errFuc[type]; // 當前綁定的監(jiān)聽函數(shù)
if(type === UNHANDLEDREJECTION){
// 沒有訂閱事件的 promise 則啥也不干
if (!this.subscribeUnhandler.size) return;
// 根據(jù)當前事件類型齿穗,選擇處理函數(shù)
changgeStateFuc = (err, promise)=>{
this.subscribeHandler.set(promise);
this.subscribeUnhandler.delete(promise, err);
}
// 不論如何當前時間循環(huán)下的等待隊列狀態(tài)全部需要變更
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 發(fā)布 catch 函數(shù),就根據(jù)當前 Promise 執(zhí)行相應方法饺律,并將其從 Handler 訂閱者里刪除
errFuc && errFuc(promise);
this.subscribeHandler.delete(promise);
}
}
}
// 定義一些靜態(tài)成員變量 默認不可寫
Object.defineProperties(PromiseSubscribePublish, {
[UNHANDLEDREJECTION]:{
value: UNHANDLEDREJECTION
},
[REJECTIONHANDLED]:{
value: REJECTIONHANDLED
}
})
module.exports = PromiseSubscribePublish;
// MyPromise.js
// ..
const PromiseSubscribePublish = require('./PromiseSubscribePublish');
const promiseSubscribePublish = new PromiseSubscribePublish();
// 事件循環(huán)最后執(zhí)行
const eventLoopEndRun = (()=>{
let unhandledPub;
let timer;
const queueHandler = [];
// 激活事件循環(huán)最后執(zhí)行
const activateRun = ()=>{
// 截流
timer && clearTimeout(timer);
timer = setTimeout(()=>{
unhandledPub && unhandledPub();
let handler = queueHandler.shift();
while(handler){
handler();
handler = queueHandler.shift();
}
},0);
}
// 設置 unhanldedReject 優(yōu)先級最高 窃页, 直接加入隊列
return (handler,immediate)=> {
immediate ? unhandledPub = handler : queueHandler.push(handler);
activateRun();
}
})()
//...
reject (err) {
this.changeStateHandler && this.changeStateHandler(REJECTED, err);
promiseSubscribePublish.subscribe(this, err);
// 存在 reject ,事件循環(huán)結束發(fā)布 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();
// 取消當前事件循環(huán)下 reject 狀態(tài)未 catch 事件訂閱;
promiseSubscribePublish.quitSubscribe(this);
if (currentState === REJECTED) {
eventLoopEndRun(()=>{
// 發(fā)布 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)
-
串聯(lián) Promise 以及 Promise 鏈返回值
看到鏈式复濒,首先想到的是 jquery 調用脖卖。jquery 返回的是 jquery 對象本體。而 Promise 根據(jù)狀態(tài)判斷:
- 當是操作成功狀態(tài)時巧颈,調用 catch 會返回和當前 Promise 的
[[PromiseStatus]]
和[[PromiseValues]]
狀態(tài)相同新構建的 Promise畦木;調用 then 方法時,返回和當前 Promise 的[[PromiseStatus]]
相同的洛二,[[PromiseValues]]
值為 then 方法返回值的 新構建的 Promise馋劈; - 當是捕獲錯誤狀態(tài)時攻锰,調用 then 會返回和當前 Promise 的
[[PromiseStatus]]
和[[PromiseValues]]
狀態(tài)相同新構建的 Promise;調用 catch 方法時妓雾, 返回操作成功的新構建的 Promise 娶吞,[[PromiseValues]]
值為 catch 方法返回值; - 當執(zhí)行 catch 或 then 方法體內有報錯械姻,直接返回一個新構建捕獲錯誤的 Promise 妒蛇,
[[PromiseValues]]
為那個錯誤; - 如果 Promise 中有一環(huán)出現(xiàn)錯誤楷拳,而鏈中沒有 catch 方法绣夺,則拋出錯誤,否則把鏈上的所有 Promise 都從
unhandledRejuect
訂閱中去除欢揖。 - 因為 then 和 catch 回調方法是當前事件循環(huán)結束時才執(zhí)行陶耍,而 catch 去除 Promise 鏈上
unhandledRejuect
訂閱是當前事件循環(huán),如果鏈上有方法報錯她混,unhandledRejuect
訂閱會再次發(fā)生烈钞,這樣會造成哪怕當前報錯 Promise 后有 catch,也會拋出錯誤坤按,因此需要給當前 Promise 加一個屬性毯欣,以標志鏈后有 catch,使得其不訂閱unhandledRejuect
事件臭脓。
- 當是操作成功狀態(tài)時巧颈,調用 catch 會返回和當前 Promise 的
分析一波:
1. 要在實例方法中酗钞,創(chuàng)建另一個當前類的實例時,必須用到當前類的構造函數(shù)来累。當咱們的類被繼承出一個派生類砚作,咱們希望返回的是那個派生類,于是不能直接 new MyPromise 去創(chuàng)建,而要使用一個 Symbol.species
2. 新建 Promise 和之前的 Promise 存在關聯(lián),所以當前 Promise 的狀態(tài)決定新 Promise 狀態(tài)佃扼,構建新 Promise 的過程中當前 Promise 的捕獲函數(shù)不能將其訂閱從 unhandledReject 中移除偎巢,所以需要一個標志位來標識 then 函數(shù)屬性。
3. Promise 鏈上如果出現(xiàn) catch 函數(shù)兼耀,?鏈上 catch 函數(shù)之前的所有 Promise 都將從訂閱 unhandledReject Map 中移除压昼,因此 Promise 需要記錄鏈上的上一級 Promise;
4. Promise then 或 catch 方法體內報錯將構建一個捕獲錯誤狀態(tài)的 Promise瘤运,因此需要一個函數(shù)去捕獲可能發(fā)生的錯誤窍霞;
//... 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);
// 根據(jù)隊列原則,執(zhí)行肯定在當前 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;
// 取消當前事件循環(huán)下 reject 狀態(tài)未 catch 事件訂閱;
// 當是實例內部調用時,不能將當前 Promise 從 unhandledReject 隊列中移除但金;
// 否則順著生成鏈依次將 Promise 移除;
if(!quitReturn)clearLinksSubscribe(this)
if (currentState === REJECTED) {
eventLoopEndRun(()=>{
// 發(fā)布 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)=>{
// 根據(jù)隊列原則郁季,執(zhí)行肯定在當前 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]函數(shù)的數(shù)據(jù), 返回一個 Promise似枕,該 Promise 成功操作盖淡,then 方法傳入一個數(shù)組,數(shù)組數(shù)據(jù)位置和迭代器迭代返回的順序相關聯(lián)凿歼,該 Promise 捕獲錯誤 catch 里的傳入捕獲的錯誤; 2褪迟、 迭代器遍歷結果如果是 Promise , 則將其 PromiseValue 作為值,插入傳入數(shù)組對應的位置答憔,當遍歷結果不是 Promise 直接插入數(shù)組對應位置味赃,當遇到捕獲錯誤,或者 Promise 出現(xiàn)錯誤時直接將狀態(tài)轉變?yōu)?rejected 狀態(tài) 虐拓,從 catch 拿到相應錯誤的值心俗;總結就是有錯馬上拋,要不等所有數(shù)據(jù)處理完才改變狀態(tài)侯嘀;
Promise.race 就不贅述:記住幾點另凌,傳入?yún)?shù)要求和 .all 相同,數(shù)據(jù)處理方式是戒幔,先到先得,率先處理完的數(shù)據(jù)直接修改狀態(tài)土童。
在分析一波之前诗茎,調整幾個之前的沒有考慮到的問題:
- 將狀態(tài)改變函數(shù)覆蓋操作移至 resolve 和 reject 函數(shù)中。
- reject 方法體執(zhí)行全都由是否能改變狀態(tài)決定献汗。
- reject 新增一個參數(shù)敢订,表示不訂閱
unhandledReject
事件,因為 then 方法也會生成新的 Promise罢吃,而 then 鏈前有捕獲異常狀態(tài)的 Promise 會造成重復報錯楚午,catch 無所謂,因為本身會Promise 鏈隊列。
// 開頭的 '-' 標示移除尿招,'+' 表示新增
// ... changeStateHandler 方法
- this.changeStateHandler = null;
resolve (data) {
if(this.changeStateHandler){
this.changeStateHandler(FULFILLED, data);
// 保持狀態(tài)只能改變一次
this.changeStateHandler = null;
}
}
reject (err, noSubscribe) {
if(this.changeStateHandler){
this.changeStateHandler(REJECTED, err);
!noSubscribe && !this.hascatch && promiseSubscribePublish.subscribe(this, err);
// 存在 reject 矾柜,事件循環(huán)結束發(fā)布 UNHANDLEDREJECTION
eventLoopEndRun(()=>
promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
true
);
// 保持狀態(tài)只能改變一次
this.changeStateHandler = null;
}
}
// then 方法
- this.catch(err=>{
reject(err)
}, true);
+ this.catch(err=>reject(err, true), true);
接下來開始分析一波:
首先咱們的判斷,傳入的是否具有
Symbol.iterator
就谜,沒有就直接拋錯(Promise 狀態(tài)會直接變?yōu)?reject怪蔑,就不往下說了);因為咱們定義的 MyPromise 所以判斷類型應該是 MyPromise丧荐,如果想要通過
Object.prototype.toString.call
去判斷缆瓣,咱們需要給咱們的類加一個 tag.all 處理完一波數(shù)據(jù)插入結果值對應的位置,判斷是否數(shù)據(jù)完全處理完虹统,如果全部處理完才改變狀態(tài)弓坞。.race 處理完那個直接改變狀態(tài)隧甚,忽略后面、忽略后面渡冻、忽略后面(重要的事情嗶嗶3次)呻逆。
兩邊如果有傳入的 Promise 狀態(tài)出現(xiàn)捕獲異常,返回的 Promise 狀態(tài)即變?yōu)楫惓#琧atch 得到的值即為傳入 Promise 異常的那個異常
繞死你菩帝。因為是靜態(tài)方法所以不能用 Symbol.species 構建實例咖城。
// MyPromise.js 最后頭
MyPromise.prototype[Symbol.toStringTag] = "MyPromise";
static all (promiseArr){
// 因為是靜態(tài)方法 無法獲取 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 應存放返回數(shù)組的位置;
let waitPromiseNum = 0; // 統(tǒng)計未完成的 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){
// 因為是靜態(tài)方法 無法獲取 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)});
結束
如果發(fā)現(xiàn)過程遇到什么問題宜雀,歡迎及時提出。