前言
Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對於這些不熟悉的可以參考我另一篇文章《JavaScript異步編程》),這不僅僅是技術實現的發展,更是思想上對於如何控制異步的遞進。Promise作爲後續方案的基礎,是重中之重,也是面試時候最常被問到的。
今天我們就一起從0到1實現一個基於A+規範的Promise,過程中也會對Promise的異常處理,以及是否可手動終止做一些討論,最後會對我們實現的Promise做單元測試。完整的代碼已經上傳到github,想直接看代碼的可以點這裏。
雖然已經有很多帶你實現Promise類的文章了,但每個人理解的程度不一樣,也許不同的文章可以帶給你不同的思考呢,那我們就開始吧。
正文
1. 基礎框架
new Promise()時接收一個executor函數作爲參數,該函數會立即執行,函數中有兩個參數,它們也是函數,分別是resolve和reject,函數同步執行一定要放在try...catch中,否則無法進行錯誤捕獲。
MyPromise.js
function MyPromise(executor) {
function resolve(value) {
}
function reject(reason) {
}
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
module.exports = MyPromise;
resolve()接收Promise成功值value,reject接收Promise失敗原因reason。
test.js
let MyPromise = require('./MyPromise.js');
let promise = new MyPromise(function(resolve, reject) {
resolve(123);
})
2. 添加狀態機
目前實現存在的問題:
- Promise是一個狀態機的機制,初始狀態爲
pending
,成功狀態爲fulfilled
,失敗狀態爲rejected
。只能從pending
->fulfilled
,或者從pending
->rejected
,並且狀態一旦轉變,就永遠不會再變了。
所以,我們需要爲Promise添加一個狀態流轉的機制。
MyPromise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
let self = this;
self.state = PENDING;
function resolve(value) {
if (self.state === PENDING) {
self.state = FULFILLED;
}
}
function reject(reason) {
if (self.state === PENDING) {
self.state = REJECTED;
}
}
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
module.exports = MyPromise;
test.js
let MyPromise = require('./MyPromise.js');
let promise = new MyPromise(function(resolve, reject) {
resolve(123);
});
promise.then(function(value) {
console.log('value', value);
}, function(reason) {
console.log('reason', reason);
})
3. 添加then
方法
Promise擁有一個then
方法,接收兩個函數 onFulfilled
和 onRejected
,分別作爲Promise成功和失敗的回調。所以,在then
方法中我們需要對狀態state
進行判斷,如果是fulfilled
,則執行onFulfilled(value)
方法,如果是rejected
,則執行onRejected(reason)
方法。
由於成功值value
和失敗原因reason
是由用戶在executor
中通過resolve(value)
和 reject(reason)
傳入的,所以我們需要有一個全局的value
和reason
供後續方法獲取。
MyPromise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
let self = this;
self.state = PENDING;
self.value = null;
self.reason = null;
function resolve(value) {
if (self.state === PENDING) {
self.state = FULFILLED;
self.value = value;
}
}
function reject(reason) {
if (self.state === PENDING) {
self.state = REJECTED;
self.reason = reason;
}
}
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
MyPromise.prototype.then = function(onFuifilled, onRejected) {
let self = this;
if (self.state === FULFILLED) {
onFuifilled(self.value);
}
if (self.state === REJECTED) {
onRejected(self.reason);
}
};
module.exports = MyPromise;
4. 實現異步調用resolve
目前實現存在的問題:
- 同步調用
resolve()
沒有問題,但如果是異步調用,比如放到setTimeout
中,因爲目前的代碼在調用then()
方法時,state
仍是pending
狀態,當timer到時候調用resolve()
把state
修改爲fulfilled
狀態,但是onFulfilled()
函數已經沒有時機調用了。
針對上述問題,進行如下修改:
MyPromise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
let self = this;
self.state = PENDING;
self.value = null;
self.reason = null;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) {
if (self.state === PENDING) {
self.state = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(function(fulfilledCallback) {
fulfilledCallback();
});
}
}
function reject(reason) {
if (self.state === PENDING) {
self.state = REJECTED;
self.reason = reason;
self.onRejectedCallbacks.forEach(function(rejectedCallback) {
rejectedCallback();
});
}
}
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
MyPromise.prototype.then = function(onFuifilled, onRejected) {
let self = this;
if (self.state === PENDING) {
self.onFulfilledCallbacks.push(() => {
onFuifilled(self.value);
});
self.onRejectedCallbacks.push(() => {
onRejected(self.reason);
});
}
if (self.state === FULFILLED) {
onFuifilled(self.value);
}
if (self.state === REJECTED) {
onRejected(self.reason);
}
};
module.exports = MyPromise;
我們添加了兩個回調函數數組onFulfilledCallbacks
和onRejectedCallbacks
,用來存儲then()
方法中傳入的成功和失敗回調。然後,當用戶調用resolve()
或reject()
的時候,修改state
狀態,並從相應的回調數組中依次取出回調函數執行。
同時,通過這種方式我們也實現了可以註冊多個then()
函數,並且在成功或者失敗時按照註冊順序依次執行。
test.js
let MyPromise = require('./MyPromise.js');
let promise = new MyPromise(function(resolve, reject) {
setTimeout(function() {
resolve(123);
}, 1000);
});
promise.then(function(value) {
console.log('value1', value);
}, function(reason) {
console.log('reason1', reason);
});
promise.then(function(value) {
console.log('value2', value);
}, function(reason) {
console.log('reason2', reason);
});
5. then返回的仍是Promise
讀過PromiseA+規範的同學肯定知道,then()
方法返回的仍是一個Promise,並且返回Promise的resolve
的值是上一個Promise的onFulfilled()
函數或onRejected()
函數的返回值。如果在上一個Promise的then()
方法回調函數的執行過程中發生了錯誤,那麼會將其捕獲到,並作爲返回的Promise的onRejected
函數的參數傳入。比如:
let promise = new Promise((resolve, reject) => {
resolve(123);
});
promise.then((value) => {
console.log('value1', value);
return 456;
}).then((value) => {
console.log('value2', value);
});
let promise = new Promise((resolve, reject) => {
resolve(123);
});
打印結果爲:
value1 123
value2 456
let promise = new Promise((resolve, reject) => {
resolve(123);
});
promise.then((value) => {
console.log('value1', value);
a.b = 2; // 這裏存在語法錯誤
return 456;
}).then((value) => {
console.log('value2', value);
}, (reason) => {
console.log('reason2', reason);
});
打印結果爲:
value1 123
reason2 ReferenceError: a is not defined
可以看到,then()
方法回調函數如果發生錯誤,會被捕獲到,那麼then()
返回的Promise會自動變爲onRejected
,執行onRejected()
回調函數。
let promise = new Promise((resolve, reject) => {
reject(123);
});
promise.then((value) => {
console.log('value1', value);
return 456;
}, (reason) => {
console.log('reason1', reason);
return 456;
}).then((value) => {
console.log('value2', value);
}, (reason) => {
console.log('reason2', reason);
});
打印結果爲:
reason1 123
value2 456
好啦,接下來我們就去實現then()
方法依然返回一個Promise。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) {
let self = this;
let promise2 = null;
promise2 = new MyPromise((resolve, reject) => {
if (self.state === PENDING) {
self.onFulfilledCallbacks.push(() => {
try {
let x = onFuifilled(self.value);
self.resolvePromise(promise2, x, resolve, reject);
} catch(reason) {
reject(reason);
}
});
self.onRejectedCallbacks.push(() => {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch(reason) {
reject(reason);
}
});
}
if (self.state === FULFILLED) {
try {
let x = onFuifilled(self.value);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}
if (self.state === REJECTED) {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}
});
return promise2;
};
可以看到,我們新增了一個promise2
作爲then()
方法的返回值。通過let x = onFuifilled(self.value)
或者 let x = onRejected(self.reason)
拿到then()
方法回調函數的返回值,然後調用self.resolvePromise(promise2, x, resolve, reject)
,將新增的promise2
、x
、promise2
的resolve
和reject
傳入到resolvePromise()
中。
所以,下面我們重點看一下resolvePromise()
方法。
MyPromise.js
MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) {
let self = this;
let called = false; // called 防止多次調用
if (promise2 === x) {
return reject(new TypeError('循環引用'));
}
if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
// x是對象或者函數
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (y) => {
// 別人的Promise的then方法可能設置了getter等,使用called防止多次調用then方法
if (called) return ;
called = true;
// 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值爲基本類型或者非thenable
self.resolvePromise(promise2, y, resolve, reject);
}, (reason) => {
if (called) return ;
called = true;
reject(reason);
});
} else {
if (called) return ;
called = true;
resolve(x);
}
} catch (reason) {
if (called) return ;
called = true;
reject(reason);
}
} else {
// x是普通值,直接resolve
resolve(x);
}
};
resolvePromise()
是用來解析then()
回調函數中返回的仍是一個Promise
,這個Promise
有可能是我們自己的,有可能是別的庫實現的,也有可能是一個具有then()
方法的對象,所以這裏靠resolvePromise()
來實現統一處理。
下面是翻譯自PromiseA+規範關於resolvePromise()
的要求:
Promise 解決過程
Promise 解決過程是一個抽象的操作,其需輸入一個 promise 和一個值,我們表示爲 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;否則其用 x 的值來執行 promise 。
這種 thenable 的特性使得 Promise 的實現更具有通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法即可;這同時也使遵循 Promise/A+ 規範的實現可以與那些不太規範但可用的實現能良好共存。
運行 [[Resolve]](promise, x) 需遵循以下步驟:
- x 與 promise 相等
如果 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
- x 爲 Promise
如果 x 爲 Promise ,則使 promise 接受 x 的狀態:
- 如果 x 處於等待態, promise 需保持爲等待態直至 x 被執行或拒絕
- 如果 x 處於執行態,用相同的值執行 promise
- 如果 x 處於拒絕態,用相同的據因拒絕 promise
- x 爲對象或函數
如果 x 爲對象或者函數:
- 把 x.then 賦值給 then
- 如果取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
- 如果 then 是函數,將 x 作爲函數的作用域 this 調用之。傳遞兩個回調函數作爲參數,第一個參數叫做 resolvePromise ,第二個參數叫做 rejectPromise:
- 如果 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
- 如果 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
- 如果 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了多次,則優先採用首次調用並忽略剩下的調用
- 如果調用 then 方法拋出了異常 e:
- 如果 resolvePromise 或 rejectPromise 已經被調用,則忽略之
- 否則以 e 爲據因拒絕 promise
- 如果 then 不是函數,以 x 爲參數執行 promise
- 如果 x 不爲對象或者函數,以 x 爲參數執行 promise
如果一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 爲據因來拒絕 promise。
參考上述規範,結合代碼中的註釋,相信大家可以理解resolvePromise()
的作用了。
測試:
test.js
let MyPromise = require('./MyPromise.js');
let promise = new MyPromise(function(resolve, reject) {
setTimeout(function() {
resolve(123);
}, 1000);
});
promise.then((value) => {
console.log('value1', value);
return new MyPromise((resolve, reject) => {
resolve(456);
}).then((value) => {
return new MyPromise((resolve, reject) => {
resolve(789);
})
});
}, (reason) => {
console.log('reason1', reason);
}).then((value) => {
console.log('value2', value);
}, (reason) => {
console.log('reason2', reason);
});
打印結果:
value1 123
value2 789
6. 讓then()
方法的回調函數總是異步調用
官方Promise
實現的回調函數總是異步調用的:
console.log('start');
let promise = new Promise((resolve, reject) => {
console.log('step-');
resolve(123);
});
promise.then((value) => {
console.log('step--');
console.log('value', value);
});
console.log('end');
打印結果:
start
step-
end
step--
value1 123
Promise屬於微任務,這裏我們爲了方便用宏任務setTiemout
來代替實現異步,具體關於宏任務、微任務以及Event Loop可以參考我的另一篇文章帶你徹底弄懂Event Loop。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) {
let self = this;
let promise2 = null;
promise2 = new MyPromise((resolve, reject) => {
if (self.state === PENDING) {
self.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFuifilled(self.value);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
self.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
}
if (self.state === FULFILLED) {
setTimeout(() => {
try {
let x = onFuifilled(self.value);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
}
if (self.state === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
}
});
return promise2;
};
測試:
test.js
let MyPromise = require('./MyPromise.js');
console.log('start');
let promise = new MyPromise((resolve, reject) => {
console.log('step-');
setTimeout(() => {
resolve(123);
}, 1000);
});
promise.then((value) => {
console.log('step--');
console.log('value', value);
});
console.log('end');
打印結果:
start
step-
end
step--
value1 123
經過以上步驟,一個最基本的Promise就已經實現完了,下面我們會實現一些不在PromiseA+規範的擴展方法。
7. 實現catch()
方法
then()
方法的onFulfilled
和onRejected
回調函數都不是必傳項,如果不傳,那麼我們就無法接收reject(reason)
中的錯誤,這時我們可以通過鏈式調用catch()
方法用來接收錯誤。舉例:
let promise = new Promise((resolve, reject) => {
reject('has error');
});
promise.then((value) => {
console.log('value', value);
}).catch((reason) => {
console.log('reason', reason);
});
打印結果:
reason has error
不僅如此,catch()
可以作爲Promise鏈式調用的最後一步,前面Promise發生的錯誤會冒泡到最後一個catch()
中,從而捕獲異常。舉例:
let promise = new Promise((resolve, reject) => {
resolve(123);
});
promise.then((value) => {
console.log('value', value);
return new Promise((resolve, reject) => {
reject('has error1');
});
}).then((value) => {
console.log('value', value);
return new Promise((resolve, reject) => {
reject('has error2');
});
}).catch((reason) => {
console.log('reason', reason);
});
打印結果:
reason has error
reason has error1
那麼catch()
方法到底是如何實現的呢?
答案就是在Promise的實現中,onFulfilled
和onRejected
函數是有默認值的:
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) {
onFuifilled = typeof onFuifilled === 'function' ? onFuifilled : value => {return value;};
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
};
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
可以看到,onRejected
的默認值是把錯誤reason
通過throw
拋出去。由於我們對於同步代碼的執行都是在try...catch
中的,所以如果Promise發生了錯誤,如果沒傳onRejected
,默認的函數會把錯誤reason
拋出,然後會被promise2捕捉到,作爲reject(reason)
決議。
catch()
實現就是調用this.then(null, onRejected)
,由於promise2
被reject
,所以會執行onRejected
回調,於是就捕捉到了第一個promise的錯誤。
總結來說,then()
方法中不傳onRejected
回調,Promise
內部會默認幫你寫一個函數作爲回調,作用就是throw
拋出reject
或者try...catch
到的錯誤,然後錯誤reason
會被promise2
作爲reject(reason)
進行決議,於是會被下一個then()
方法的onRejected
回調函數調用,而catch
只是寫了一個特殊的then(null, onRejected)
而已。
所以,我們在寫Promise
的鏈式調用的時候,在then()
中可以不傳onRejected
回調,只需要在鏈式調用的最末尾加一個catch()
就可以了,這樣在該鏈條中的Promise
發生的錯誤都會被最後的catch
捕獲到。
舉例1:
let promise = new Promise((resolve, reject) => {
reject(123);
});
promise.then((value) => {
// 注意,不會走這裏,因爲第一個promise是被reject的
console.log('value1', value);
return new Promise((resolve, reject) => {
reject('has error1');
});
}).then((value) => {
console.log('value2', value);
return new Promise((resolve, reject) => {
reject('has error2');
});
}, (reason) => {
// 注意,這個then有onRejected回調
console.log('reason2', reason);
}).catch((reason) => {
// 錯誤在上一個then就被捕獲了,所以不會走到這裏
console.log('reason3', reason);
});
打印結果:
reason2 123
舉例2:
let promise = new Promise((resolve, reject) => {
reject(123);
});
promise.then((value) => {
console.log('value1', value);
return new Promise((resolve, reject) => {
reject('has error1');
});
}).then((value) => {
console.log('value2', value);
return new Promise((resolve, reject) => {
reject('has error2');
});
}).catch((reason) => {
// 由於鏈條中的then都沒有onRejected回調,所以會一直被冒泡到最後的catch這裏
console.log('reason3', reason);
});
catch
和then
一樣都是返回一個新的Promise
。有的同學可能會有疑問,如果catch
中的回調執行也發生錯誤該怎麼辦呢,這個我們後續在Promise異常處理中再做討論。
打印結果:
reason3 123
8. 實現finally
方法
finally
是某些庫對Promise
實現的一個擴展方法,無論是resolve
還是reject
,都會走finally
方法。
MyPromise.js
MyPromise.prototype.finally = function(fn) {
return this.then(value => {
fn();
return value;
}, reason => {
fn();
throw reason;
});
};
9. 實現done
方法
done
方法作爲Promise
鏈式調用的最後一步,用來向全局拋出沒有被Promise
內部捕獲的錯誤,並且不再返回一個Promise
。一般用來結束一個Promise
鏈。
MyPromise.js
MyPromise.prototype.done = function() {
this.catch(reason => {
console.log('done', reason);
throw reason;
});
};
10. 實現Promise.all
方法
Promise.all()
接收一個包含多個Promise
的數組,當所有Promise
均爲fulfilled
狀態時,返回一個結果數組,數組中結果的順序和傳入的Promise
順序一一對應。如果有一個Promise
爲rejected
狀態,則整個Promise.all
爲rejected
。
MyPromise.js
MyPromise.all = function(promiseArr) {
return new MyPromise((resolve, reject) => {
let result = [];
promiseArr.forEach((promise, index) => {
promise.then((value) => {
result[index] = value;
if (result.length === promiseArr.length) {
resolve(result);
}
}, reject);
});
});
};
test.js
let MyPromise = require('./MyPromise.js');
let promise1 = new MyPromise((resolve, reject) => {
console.log('aaaa');
setTimeout(() => {
resolve(1111);
console.log(1111);
}, 1000);
});
let promise2 = new MyPromise((resolve, reject) => {
console.log('bbbb');
setTimeout(() => {
reject(2222);
console.log(2222);
}, 2000);
});
let promise3 = new MyPromise((resolve, reject) => {
console.log('cccc');
setTimeout(() => {
resolve(3333);
console.log(3333);
}, 3000);
});
Promise.all([promise1, promise2, promise3]).then((value) => {
console.log('all value', value);
}, (reason) => {
console.log('all reason', reason);
})
打印結果:
aaaa
bbbb
cccc
1111
2222
all reason 2222
3333
11. 實現Promise.reace
方法
Promise.race()
接收一個包含多個Promise
的數組,當有一個Promise
爲fulfilled
狀態時,整個大的Promise
爲onfulfilled
,並執行onFulfilled
回調函數。如果有一個Promise
爲rejected
狀態,則整個Promise.race
爲rejected
。
MyPromise.js
MyPromise.race = function(promiseArr) {
return new MyPromise((resolve, reject) => {
promiseArr.forEach(promise => {
promise.then((value) => {
resolve(value);
}, reject);
});
});
};
test.js
let MyPromise = require('./MyPromise.js');
let promise1 = new MyPromise((resolve, reject) => {
console.log('aaaa');
setTimeout(() => {
resolve(1111);
console.log(1111);
}, 1000);
});
let promise2 = new MyPromise((resolve, reject) => {
console.log('bbbb');
setTimeout(() => {
reject(2222);
console.log(2222);
}, 2000);
});
let promise3 = new MyPromise((resolve, reject) => {
console.log('cccc');
setTimeout(() => {
resolve(3333);
console.log(3333);
}, 3000);
});
Promise.all([promise1, promise2, promise3]).then((value) => {
console.log('all value', value);
}, (reason) => {
console.log('all reason', reason);
})
打印結果:
aaaa
bbbb
cccc
1111
all reason 1111
2222
3333
12. 實現Promise.resolve
方法
Promise.resolve
用來生成一個fulfilled
完成態的Promise
,一般放在整個Promise
鏈的開頭,用來開始一個Promise
鏈。
MyPromise.js
MyPromise.resolve = function(value) {
let promise;
promise = new MyPromise((resolve, reject) => {
this.prototype.resolvePromise(promise, value, resolve, reject);
});
return promise;
};
test.js
let MyPromise = require('./MyPromise.js');
MyPromise.resolve(1111).then((value) => {
console.log('value1', value);
return new MyPromise((resolve, reject) => {
resolve(2222);
})
}).then((value) => {
console.log('value2', value);
})
打印結果:
value1 1111
value2 2222
由於傳入的value
有可能是普通值,有可能是thenable
,也有可能是另一個Promise
,所以調用resolvePromise
進行解析。
12. 實現Promise.reject
方法
Promise.reject
用來生成一個rejected
失敗態的Promise
。
MyPromise.js
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
};
test.js
let MyPromise = require('./MyPromise.js');
MyPromise.reject(1111).then((value) => {
console.log('value1', value);
return new MyPromise((resolve, reject) => {
resolve(2222);
})
}).then((value) => {
console.log('value2', value);
}).catch(reason => {
console.log('reason', reason);
});
打印結果:
reason 1111
13. 實現Promise.deferred
方法
Promise.deferred
可以用來延遲執行resolve
和reject
。
MyPromise.js
MyPromise.deferred = function() {
let dfd = {};
dfd.promies = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.rfeject = reject;
});
return dfd;
};
這樣,你就可以在外部通過調用dfd.resolve()
和dfd.reject()
來決議該Promise
。
13. 如何停止一個Promise
鏈
假設這樣一個場景,我們有一個很長的Promise
鏈式調用,這些Promise
是依次依賴的關係,如果鏈條中的某個Promise
出錯了,就不需要再向下執行了,默認情況下,我們是無法實現這個需求的,因爲Promise
無論是then
還是catch
都會返回一個Promise
,都會繼續向下執行then
或catch
。舉例:
new Promise(function(resolve, reject) {
resolve(1111)
}).then(function(value) {
// "ERROR!!!"
}).catch()
.then()
.then()
.catch()
.then()
有沒有辦法讓這個鏈式調用在ERROR!!!的後面就停掉,完全不去執行鏈式調用後面所有回調函數呢?
我們自己封裝一個Promise.stop
方法。
MyPromise.js
MyPromise.stop = function() {
return new Promise(function() {});
};
stop
中返回一個永遠不執行resolve
或者reject
的Promise
,那麼這個Promise
永遠處於pending
狀態,所以永遠也不會向下執行then
或catch
了。這樣我們就停止了一個Promise
鏈。
new MyPromise(function(resolve, reject) {
resolve(1111)
}).then(function(value) {
// "ERROR!!!"
MyPromise.stop();
}).catch()
.then()
.then()
.catch()
.then()
但是這樣會有一個缺點,就是鏈式調用後面的所有回調函數都無法被垃圾回收器回收。
14. 如何解決Promise
鏈上返回的最後一個Promise
出現錯誤
看如下例子:
new Promise(function(resolve) {
resolve(42)
}).then(function(value) {
a.b = 2;
});
這裏a不存在,所以給a.b賦值是一個語法錯誤,onFulfilled
回調函數是包在try...catch
中執行的,錯誤會被catch
到,但是由於後面沒有then
或catch
了,這個錯誤無法被處理,就會被Promise
吃掉,沒有任何異常,這就是常說的Promise有可能會吃掉錯誤。
那麼我們怎麼處理這種情況呢?
方法一
就是我們前面已經實現過的done()
。
new Promise(function(resolve) {
resolve(42)
}).then(function(value) {
a.b = 2;
}).done();
done()
方法相當於一個catch
,但是卻不再返回Promise
了,注意done()
方法中不能出現語法錯誤,否則又無法捕獲了。
方法二
普通錯誤監聽window
的error
事件可以實現捕獲
window.addEventListener('error', error => {
console.log(error); // 不會觸發
});
Promise沒有被onRejected()
處理的錯誤需要監聽unhandledrejection
事件
window.addEventListener('unhandledrejection', error => {
console.log('unhandledrejection', error); // 可以觸發,而且還可以直接拿到 promise 對象
});
14. 單元測試
結束
相關單元測試以及完整代碼可以到我的github查看,如果對你有幫助的話,就來個star吧~