手把手教你按照PromiseA+規範來實現Promise

該實現是按照promiseA+規範來進行梳理的 當使用promise的時候須要先new一個實例,因此咱們要構造一個promise構造函數jquery

let p = new Promise(function(resolve, reject){
    // 先調誰,就走誰
    reject('不對了');  // 失敗
    resolve(1);  // 成功  

    // 若是拋出異常也會走到then的失敗函數裏
    throw new Error('錯了'); 
});
p.then(function(data) {
    console.log(data);    
}, function(err) {
    console.log(err);      // '不對了'
});
複製代碼

new的Promise實例包含一個執行函數(executor),該函數有兩個參數,分別是成功和失敗的方法npm

這裏要記住一點是成功和失敗的方法是互斥的,同時調用時,先調用的先執行,後面調用的不會再執行segmentfault

狀態

都知道promise是有三種狀態值的數組

  • pending(待定) 默認的狀態
  • fulfilled(成功)
  • rejected(失敗)

那麼給構造函數寫一個status,用來保存狀態的變化 pendding能夠轉爲fulfilled或者rejected, 一旦轉換了,status就不會改變了,要麼是成功態,要麼是失敗態promise

// 實現promise
function Promise(executor) {
	const self = this;
	self.status = 'pending';	// 默認的狀態,pending->fulfilled, pending->rejected
	self.value = null;		// 成功的值
	self.reason = null;		// 失敗的緣由

	function resolve(value) {	// 成功
	    if (self.status === 'pending') {
		self.status = 'fulfilled';
		self.value = value;
	    }
	}
	function reject(reason) {		// 失敗
	    if (self.status === 'pending') {
		self.status = 'rejected';
		self.reason = reason;
            }
	}


	try {
	    executor(resolve, reject);
	} catch(e) {		// 若是拋出錯誤,就直接走失敗的方法
	    reject(e);
	}
}

Promise.prototype.then = function(onFulfilled, onRejected) {
	const self = this;
	if (self.status === 'fulfilled') {
	    onFulfilled();
	}
	if (self.status === 'rejected') {
	    onRejected();
	}
};

module.exports = Promise;
複製代碼

上面寫了不少,可是發現都是處理同步的狀況,當遇到異步的時候該怎麼處理呢,咱們繼續往下寫bash

異步promise

promise中能夠寫異步代碼,而且能夠進行屢次then異步

let p = new Promise(function(resolve, reject){
    // 異步操做
    setTimeout(function() {
        resolve('ok');
    }, 1000);
}).then(function(data) {
    console.log(data);    // 'ok'
    return `下面的人接着  + ${data}`;
}, function(err) {
    console.log(err);     
}).then(function(data) {
    console.log(data);    // '下面的人接着 ok'
}, function(err) {
    console.log(err);     
});
複製代碼
實現一下異步的promise

promise實例能夠屢次then,當成功後會將then中的成功的方法依次執行, 咱們能夠先將then中成功的回調和失敗的回調存到數組內,當成功時調用成功的數組便可函數

function Promise() {
    self.status = 'pending';
    self.value = null;
    self.reason = null;
+   self.onFulfilledCb = [];    // 存放then成功的回調
+   self.onRejectedCb = [];     // 存放then失敗的回調   

    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'fulfilled';
            self.value = value;
     +      self.onFulfilledCb.forEach(function (fn) {
                fn();
            });
        }
    }
    
    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
      +     self.onRejectedCb.forEach(function (fn) {
                fn();
            });
        }
    }  

    try {
        executor(resolve, reject);
    } catch(e) {        // 若是拋出錯誤,就直接走失敗的方法
        reject(e);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    if (self.status === 'fulfilled') {
        onFulfilled(self.value);
    } 
    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
    // 當調用then時 可能沒成功也沒失敗,就處於pending狀態

+   if (self.status === 'pending') {
        // 將成功的回調添加到數組中
        self.onFulfilledCb.push(function () {
            onFulfilled(self.value);
        });
        self.onRejectedCb.push(function () {
            onRejected(self.reason);
        });
    }
};

module.exports = Promise;
複製代碼

鏈式調用

上面的代碼實現了異步調用的時候then方法是怎麼處理的狀況,測試

那麼接下來就來到重要的環節了,你們都知道promise的then方法也是支持鏈式調用的ui

不過then的鏈式調用可不一樣於jquery返回this的方式實現的

☆️ 它是返回了一個新的promise

下面來看代碼

let p = new Promise(function(resolve, reject) {
        resolve('ok');
});
p.then(function (data) {
    console.log(data);
    return data;
}, function (err) {
    console.log(err);
    // return '有返回值就走成功態'
}).then(function (data) {
    console.log('2 '+data);
}, function (err) {
    console.log(err);
})
複製代碼

now讓咱們開始實現鏈式調用,仍是老樣子,+號表明實現的主要代碼:

Promise.prototype.then = function (onFulfilled, onRejected) {
    const self = this;
 +  let promise2;    // 定義一個promise2變量

    if (self.status === 'fulfilled') {
         // 返回一個新的promise
 +      promise2 = new Promise(function (resolve, reject) {
            // x多是個普通值,也多是個promise
 +          let x = onFulfilled(self.value);
            // x也多是別人寫的promise,寫一個函數統一去處理
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    if (self.status === 'rejected') {
 +       promise2 = new Promise(function (resolve, reject) {
 +          let x = onRejected(self.reason);
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    // 當調用then時 可能沒成功也沒失敗,就處於pending狀態
    if (self.status === 'pending') {
        // 將成功的回調添加到數組中
+        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               let x = onFulfilled(self.value);
+               resolvePromise(promise2, x, resolve, reject);
            });

            self.onRejectedCb.push(function () {
+               let x = onRejected(self.reason);
+               resolvePromise(promise2, x, resolve, reject);
            });
        });
    }

    return promise2;
};
function resolvePromise(p2, x, resolve, reject) {
    if (p2 === x) {    // 不能返回本身
        return reject(new TypeError('循環引用'));
    }
    let called;     // 表示是否調用成功or失敗
    // x返回的多是對象和函數也多是一個普通的值
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, function (y) {
                    // 防止屢次調用
                    if (called) return;
                    called = true;
                    // y可能仍是個promise,因此遞歸繼續解析直到返回一個普通值
                    resolvePromise(p2, y, resolve, reject);
                }, function (e) {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else {    
                // 處理then不是函數的狀況,如{then: 1},就直接返回成功
                resolve(x);
            }
        } catch(e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);     // 返回一個普通值
    }
}
複製代碼

經過以上代碼也能夠得出了一些結論

  • 若是then中不管是成功的回調仍是失敗的回調有return返回值,都會走下一個then的成功
  • 若是第一個promise返回了一個普通值,會進入下一次then的成功回調;若是第一個promise返回了一個新的promise,須要等待返回promise執行的結果傳遞給下一次的then中
  • resolvePromise函數,返回的結果和promise是同一個,既不會成功也不會失敗,咱們就報一個類型錯誤提示一下

以上咱們已經實現了promise的鏈式調用了,但這還不夠promise有一種狀況是在then中什麼都不傳的狀況,還繼續鏈式調用

let p = new Promise(function (resolve, reject) {
    reject(999);
});
p.then().then().then(function (data) {
    console.log(data);  // 999    
}, function (err) {
    console.log(err);
});
複製代碼

這就是promise中值的穿透,該實現是在then中,那麼咱們也對then方法加以改良去實現一下

Promise.prototype.then = function(onFulfilled, onRejected) {
+    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value
    };
+    onRejected = typeof onRejected === 'function' ? onFulfilled : function (err) {
        throw err
    };  
    const self = this;
    let promise2;
};
複製代碼

其實很簡單,就是在then的時候若是你不給我傳回調函數,那麼我就判斷一下,確實沒有傳,那我就給你新建立一個函數而後在成功回調的時候把值傳到下一個then中去

另外在promise規範中要求,全部的onFulfilled和onRejected都須要異步執行,因此咱們要給他們加上異步,顯然這個更容易實現了,看下面代碼

Promise.prototype.then = function(onFulfilled, onRejected) {
    if (self.status === 'fulfilled') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或失敗執行時,有異常那麼返回的promise應該處於失敗態
            // 用try/catch來防止返回異常的狀況
            // 加上setTimeout實現異步調用
+           setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
+           setTimeout(function () {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};
    if (self.status === 'pending') {
        // 將成功的回調添加到數組中
        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            });
           
            self.onRejectedCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
        });
    }
    
    return promise2;
複製代碼

好了,咱們以上就實現了promise中最重要的then方法了,寫的很差請多理解吧。

不過既然把then都寫完了,那接下來再寫幾個其餘的,寫多點也讓你們一塊兒來研究研究。

其餘方法

捕獲錯誤的catch方法

先來看下用法,其實這個和then方法裏失敗的回調onRejected是同樣的

let p = new Promise(function(resolve, reject) {
    reject('錯錯錯');
});
p.then(function(data){
    console.log(data);
}).catch(function(e){
    console.log(e);    // '錯錯錯'
});
複製代碼

下面上代碼:

// 捕獲錯誤的方法
Promise.prototype.catch = function(callback) {
     // 也是調用then方法,給成功的回調傳一個null,給失敗的回調傳入callback
    return this.then(null, callback); 
};
複製代碼

Promise類自身也有一些方法,經常使用的有all, race, resolve, reject

都寫到這裏了,那就繼續寫下去,廢話很少說,開擼!!!

all方法

Promise.all()的用法

let p = new Promise(function(resolve, reject) {
   setTimeout(function() {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('南京');
    }, 200);
});
let p3 = new Promise(function(resolve, reject) {
    resolve('東京');
});
Promise.all([p, p2, p3]).then(function(data) {
    // all方法是全部的promise都成功後纔會成功
    // 按照傳入all裏的順序依次執行,p裏面的定時器只是執行完成的時間而已,不影響all裏輸出順序
    // 如:p這個須要等1秒後纔會返回成功態,p2就須要等待1秒時間
    console.log(data);    // [ '北京', '南京', '東京' ]
});
複製代碼

上面知道了用法,下面就來寫實現,客官且留步繼續看下去

Promise.all = function(items) {
    return new Promise(function(resolve, reject) {
        let res = [];    // 用來存儲成功函數返回的值
        let num = 0;  // 記錄都返回成功的數字
        let len = items.length;  // 數組的長度 
        // 對數組進行遍歷
        for (let i = 0; i < len; i++) {
              items[i].then(function(data) {
                  res[i] = data;
                  if (++num === len) {
                      resolve(res);
                  }
              }, reject);
        }
    });
};
複製代碼

如此這般,這般如此,就實現了all

再接再礪還有race方法

那麼race方法顧名思義,race就是比賽,就是在比誰快,誰先成功,返回的就是誰的成功數據

race方法

來看一下怎麼用

let p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('南京');
    }, 500);
});
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('東京');
    });
});
Promise.race([p, p2, p3]).then(function (data) {
    // p3最早執行完返回成功,因此data就爲p3的值,其餘的p,p2都不返回
    console.log(data);  // '東京'
});
複製代碼

那麼,不囉嗦了,寫吧

Promise.race = function(items) {
    return new Promise(function(resolve, reject) {
        for (let i = 0; i < items.length; i++) {
            items[i].then(resolve, reject);
        }
    });
};
複製代碼

以上就實現了race方法,那麼一氣呵成再寫下resolve和reject吧

這兩個方法看起來就很是簡單,由於以前的鋪墊已經很好了基本屬於直接用的狀態 仍是先來看下用法吧

resolve和reject方法
// 成功
Promise.resolve([1,2,3,4]).then(function(data) {
    console.log(data);  // [1,2,3,4]
});
// 失敗
Promise.reject('err!').then(function () {}, function (err) {
    console.log(err);   // 'err!'
});
複製代碼

來,快點,開寫,不猶豫

Promise.resolve = function(value) {
    return new Promise(function(resolve, reject) {
        resolve(value);
    });
};
Promise.reject = function (reason) {
    return new Promise(function (resolve, reject) {
        reject(reason);
    });
};
複製代碼

完成!!!

各位客官,是否是以爲感受身體被掏空了,寫了這麼多終於完事了,能夠來梳理一下了。 錯了,還有最後一個方法沒寫,它就是defer

defer方法的做用是什麼呢?

首先它不須要寫new來生成promise實例了,

其次是由於promise的測試庫promises-aplus-tests須要寫這麼一個方法,哈哈哈

看下怎麼用吧,其實和Q是同樣的

function read() {
    let defer = Promise.defer();  // Q裏寫的是Q.defer()
    require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
read().then(function (data) {
    console.log(data)
},function(err){
    console.log(err);
})
複製代碼

爲了測試庫的須要,實現最後一個吧

Promise.defer = Promise.deferred = function() {
    let def = {};
    def.promise = new Promise(function(resolve, reject) {
        def.resolve = resolve;
        def.reject = reject;
    });

    return def;
}
複製代碼

終於,完成了全部的實現了。實屬不易,也謝謝你們堅持看完,但願有所收穫吧!哈哈

  • 最後的最後,當各位客官也在嘗試着實現promise的時候
  • 別忘記把寫完的代碼進行測試一下
  • promiseA+規範也有一個專門的測試庫來跑通你實現的promise到底如何
    • 全局安裝 npm i promises-aplus-tests -g
    • promises-aplus-tests 項目.js
  • 若是跑徹底是√,那麼恭喜你,你又多了一項技能了
相關文章
相關標籤/搜索