自己經(jīng)常使用Promise,但是對其實現(xiàn)和標(biāo)準(zhǔn)一知半解。本文從標(biāo)準(zhǔn)和實現(xiàn)出發(fā),探究下它到底干了什么。
定義
Promise是一種異步編程的解決方案,可以讓我們脫離異步回調(diào)的噩夢。我們可以把它看做一個容器,里面存放著未來才會結(jié)束的事件(如 異步回調(diào))的結(jié)果。
基本用法
下面是一個使用Promise進(jìn)行ajax請求的例子:
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
getJson 方法返回一個Promise對象,通過then完成對請求結(jié)果的處理。
標(biāo)準(zhǔn)
Promise的標(biāo)準(zhǔn)可以參見:Promise標(biāo)準(zhǔn),總結(jié)起來有以下幾條:
- Promise有三種狀態(tài):
pending、fulfilled、rejected。pending是初始狀態(tài),Promise可以由pending轉(zhuǎn)成fulfilled或者rejected。當(dāng)Promise不是pending狀態(tài)時,其狀態(tài)不能進(jìn)行改變,也就是說不可能由fulfilled或rejected狀態(tài)轉(zhuǎn)成其它狀態(tài)。 - Promise必須提供一個
then方法。then有兩個可選參數(shù):resolve和reject。只有當(dāng)Promise進(jìn)入fulfilled狀態(tài)時才會執(zhí)行resolve方法;同理進(jìn)入rejected狀態(tài)時才會執(zhí)行reject方法。then返回的是一個新的Promise對象,而不是this(原因:Promise進(jìn)入非pending狀態(tài)后,不能再改變狀態(tài)?。?。 - 不同Promise的實現(xiàn)可以相互調(diào)用。相互調(diào)用的準(zhǔn)則見標(biāo)準(zhǔn)頁面的2.3這一條。
實現(xiàn)
Promise的實現(xiàn)有很多:Es6、kriskowal/q等。我們嘗試實現(xiàn)一個基本的Promise。
構(gòu)造函數(shù)
定義一個Promise函數(shù),參數(shù)為executor。定義初始狀態(tài)為pending,同時提供兩個函數(shù)resolve和reject作為參數(shù)供executor函數(shù)調(diào)用:
function Promise(executor) {
var self = this;
// 初始狀態(tài)
self.status = 'pending';
// Promise resolve時的回調(diào)函數(shù)集,在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
self.onResolvedCallback = [];
// Promise reject時的回調(diào)函數(shù)集,因為在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
self.onRejectedCallback = [];
// resolve
function resolve(value) {
// todo
}
// reject
function reject(reason) {
// todo
}
// 考慮到執(zhí)行executor的過程中有可能出錯,所以用try/catch塊給包起來,并且在出錯后以catch到的值reject掉這個Promise
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
resolve和reject函數(shù)
resolve函數(shù)執(zhí)行時,會把Promise狀態(tài)由pending 轉(zhuǎn)成 fulfilled;reject函數(shù)執(zhí)行時,會把Promise狀態(tài)由pending 轉(zhuǎn)成 rejected。兩個函數(shù)執(zhí)行時,需要把加到Promise上的方法執(zhí)行:
function Promise(executor) {
// ...
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function() { // 異步執(zhí)行所有的回調(diào)函數(shù)
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
}
function reject(reason) {
setTimeout(function() { // 異步執(zhí)行所有的回調(diào)函數(shù)
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
}
// ...
}
then函數(shù)
then 函數(shù)返回一個Promise對象,有兩個參數(shù):onResolved 和 onRejected。它根據(jù)當(dāng)前Promise的狀態(tài)分開進(jìn)行處理:
- 當(dāng)前為
resolved,直接調(diào)用onResolved - 當(dāng)前為
reject,直接調(diào)用onRejected - 當(dāng)前為
pending,并不能確定調(diào)用onResolved還是onRejected,只能等到Promise的狀態(tài)確定后,才能確實如何處理。
// then函數(shù)
Promise.prototype.then = function(onResolved, onRejected) {
var self = this;
var promise2;
// resolve函數(shù) 如果不定義采用空函數(shù)
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
// reject函數(shù) 如果不定義采用空函數(shù)
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
}
// 分三個狀態(tài)進(jìn)行處理
if (self.status === 'resolved') {
// 如果promise1(此處即為this/self)的狀態(tài)已經(jīng)確定并且是resolved,我們調(diào)用onResolved
// 因為考慮到有可能throw,所以我們將其包在try/catch塊里
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise對象,直接取它的結(jié)果做為promise2的結(jié)果
x.then(resolve, reject)
}
resolve(x) // 否則,以它的返回值做為promise2的結(jié)果
} catch (e) {
reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結(jié)果
}
})
}
// 此處與前一個if塊的邏輯幾乎相同,區(qū)別在于所調(diào)用的是onRejected函數(shù),就不再做過多解釋
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {
// 如果當(dāng)前的Promise還處于pending狀態(tài),我們并不能確定調(diào)用onResolved還是onRejected,
// 只能等到Promise的狀態(tài)確定后,才能確實如何處理。
// 所以我們需要把我們的**兩種情況**的處理邏輯做為callback放入promise1(此處即this/self)的回調(diào)數(shù)組里
// 邏輯本身跟第一個if塊內(nèi)的幾乎一致,此處不做過多解釋
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
不同Promise的調(diào)用
這個部分參考協(xié)議,具體實現(xiàn)如下:
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
擴(kuò)展
現(xiàn)行的Promise都進(jìn)行了擴(kuò)展,如引入了catch、cancel、stop等方法。它們的實現(xiàn)也比較簡單,基于這個進(jìn)行展開:
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
Promise.cancel = Promise.stop = function() {
return new Promise(function() {})
}
總結(jié)
在參考文章2里作者舉出了三個問題,具體可以查閱問題,在此羅列下:
- 性能問題:由于setTimeout的執(zhí)行時間間隔(4ms),如果Promise過長,會影響執(zhí)行快慢
- 停止一個Promise鏈:可以返回一個空的Promise,維持在
pending狀態(tài);但這樣會造成垃圾不會回收。
new Promise(function(resolve, reject) {
resolve(42)
}).then(function(value) {
// "Big ERROR!!!"
return new Promise(function(){})
})
- Promise鏈上最后一個then出錯:現(xiàn)行的框架采用引入done函數(shù),也可以通過onRejectedCallback數(shù)組的長度判斷是不是最后一個Promise。
Promise.prototype.done = function(){
return this.catch(function(e) { // 此處一定要確保這個函數(shù)不能再出錯
console.error(e)
})
}
function reject(reason) {
setTimeout(function() {
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
if (self.onRejectedCallback.length === 0) {
console.error(reason)
}
for (var i = 0; i < self.rejectedFn.length; i++) {
self.rejectedFn[i](reason)
}
}
})
}
參考
Promise標(biāo)準(zhǔn)
剖析Promise內(nèi)部結(jié)構(gòu),一步一步實現(xiàn)一個完整的、能通過所有Test case的Promise類
Speed up your Websites with a Faster setTimeout using soon()