以 Promise/A+ 作爲標準,編寫一個可經過標準測試的Promise類庫。git
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
const PENDING = 'pending';
class MPromise{
constructor(resolver){
if(resolver && typeof resolver !== 'function'){
throw new Error('MPromise resolver is not function');
}
this.state = PENDING; //當前promise對象的狀態
this.data = undefined; //當前promise對象的數據(成功或失敗)
this.callbackQueue = []; //當前promise對象註冊的回調隊列
if(resolver){
executeResolver.call(this, resolver);
}
}
then(){
//todo
}
}
複製代碼
因此,一個 Promise構造函數 和一個實例方法then 就是Promise的核心的了,其它的都是Promise的語法糖或者說是擴展github
構造器的初始化,使用 new Promise(function(resolve, reject){...})
實例化 Promise 時,去改變promise的狀態,是執行 resolve() 或 reject()方法,那麼,resolver的兩個參數分別是成功的操做函數和失敗的操做函數。promise
function executeResolver(resolver){
let called = false; //狀態變動不可逆
let _this = this;
function onError(reason){
if(!called){
return;
}
called = true;
executeCallback.call(_this,'reject', reason);
}
function onSuccess(value){
if(!called){
return;
}
called = true;
executeCallback.call(_this, 'resolve', value);
}
try{
resolver(onSuccess, onError);
}catch(e){
onError(e);
}
}
複製代碼
這裏將上面執行 resolver 的方法抽象出來,內部再將 resovle 和 reject 兩個參數包裝成 成功和失敗的回調。瀏覽器
由於執行 resolve() 或 reject() 內部主要做用是更改當前實例的狀態爲 rejected 或 resolved,而後執行當前實例 then() 中註冊的 成功或失敗的回調函數, 因此從過程上來看,大體是相同的,抽象出來共用安全
function executeCallback(type, x){
const isResolve = type === 'resolve' ? true : false;
let thenable;
if(isResolve && typeof x === 'object' && typeof x.then === 'function'){
try {
thenable = getThen(x);
} catch (e) {
return executeCallback.call(this, 'reject', e);
}
}
if(isResolve && thenable){
executeResolver.call(this, thenable); //注意是this
} else {
this.state = isResolve ? RESOLVED : REJECTED;
this.data = x;
this.callbackQueue.forEach(v => v[type](x));
}
return this;
}
複製代碼
function getThen(obj){
const then = obj && obj.then;
if(obj && typeof obj === 'object' && typeof then === 'function'){
return applyThen(){
then.apply(obj, arguments)
}
}
}
複製代碼
標準中規定:bash
class MPromise{
...
then(onResolved, onRejected){
//回調不是函數,能夠忽略
if(this.state === RESOLVED && onResolved !== 'function'
|| this.state === REJECTED && onRejected !== 'function'){
return this;
}
let promise = new MPromise();
if(this.state !== PENDING){
var callback = this.state === RESOLVED ? onResolved : onRejected;
//注意:傳入promise,
//異步調用
executeCallbackAsync.call(promise, callback, this.data);
} else {
this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected))
}
return promise; //必須返回promise,才能鏈式調用
}
}
複製代碼
上面將異步調用callback的邏輯抽象成了一個方法executeCallbackAsync ,這個方法主要功能是安全的執行callback方法:app
function executeCallbackAsync(callback, value){
let _this = this;
setTimeout(() => {
let res;
try{
res = callback(value);
}catch(e){
return executeCallback.call(_this, 'reject', e);
}
if(res !== _this){
return executeCallback.call(_this, 'resolve', res);
} else {
return executeCallback.call(_this, 'reject', new TypeError('Cannot resolve promise with itself'));
}
}, 4);
}
複製代碼
注意這裏最好不要用 setTimeout ,使用 setTimeout 能夠異步執行回調,但其實並非真正的異步線程,而是利用了瀏覽器的 Event Loop 機制去觸發執行回調,而瀏覽器的事件輪循時間間隔是 4ms ,因此鏈接的調用 setTimeout 會有 4ms 的時間間隔,而在Nodejs 中的 Event Loop 時間間隔是 1ms,因此會產生必定的延遲,若是promise鏈比較長,延遲就會越明顯,這裏能夠引入NPM上的 immediate 模塊來異步無延遲的執行回調。異步
上面then中對於回調的處理,使用了一個回調對象來管理註冊的回調,將回調按順序添加至 callbackQueue 隊列中,調用時,依次調用。函數
class CallbackItem {
constructor(promise, onResolved, onRejected) {
this.promise = promise;
this.onResolved = typeof onResolved === 'function' ? onResolved : function (v) {
return v;
};
this.onRejected = typeof onRejected === 'function' ? onRejected : function (v) {
throw v;
};
}
resolve(value) {
executeCallbackAsync.call(this.promise, this.onResolved, value);
}
reject(value) {
executeCallbackAsync.call(this.promise, this.onRejected, value);
}
}
複製代碼
以上參考深刻理解Promise中的文章,實現MPromiseoop
function fn() {
let promise1 = new MPromise((resolve, reject) => {
resolve(1);
});
new MPromise((resolve, reject) => {
resolve(promise1); //系統執行promise1.then
}).then(res => {
console.log(res);
return 222;
}).catch(err => {
console.log(err);
});
}
fn(); // 1
複製代碼