前言
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);
});
複製程式碼
列印結果:
value 123
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.race
方法
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.race([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吧~