實現一個自己的Promise庫
在上一篇中我們介紹了整個js非同步發展的過程,今天我們就來自己完成一個Promise庫,可以參照Promise的實現規範
首先先搭一個最簡單的Promise框架
function Promise(executor) { // executor是一個執行函式
let self = this;
self.status = 'pending';
self.value = undefined; // 預設成功的值
self.reason = undefined; // 預設失敗的原因
function resolve(value){ // 成功狀態
if(self.status === 'pending'){
self.status = 'resolved';
self.value = value;
}
}
function reject(reason){ // 失敗狀態
if(self.status === 'pending'){
self.status = 'rejected';
self.reason = reason;
}
}
executor(resolve,reject)
}
Promise.prototype.then = function(onFulfilled,onRjected){
let self = this;
if(self.status === 'resolved'){
onFulfilled(self.value);
}
if(self.status === 'rejected'){
onRjected(self.reason);
}
}
module.exports = Promise
複製程式碼
這裡可以看到我們給Promise物件定義了3種狀態以及一個用來當做預設成功會傳入的值value和失敗會傳入的原因reason,同時定義了resolve和reject方法,將它們傳入executor,也就是我們在呼叫生成Promise物件時會傳入的執行函式,裡面放著我們需要呼叫的非同步邏輯.
接著我們通過Promise的原型定義了一個then的公有方法,它會接受兩個引數onFullfilled和onRejected,這兩個引數其實就是兩個函式,是我們用來定義成功或失敗時所執行的邏輯.
但是這裡的問題是當我們executor裡執行的是非同步邏輯時,如果我們呼叫then,其實status依舊是pending的狀態也就根本不會呼叫到onFullfilled或onRejected,所以我們需要定義兩個callback陣列來存放我們在pending狀態下呼叫過的onFullfilled和onRejected函式,同時,在resolve和reject函式中我們需要將對應的callback陣列中的onFullfilled和onRejected依次取出呼叫.
所以讓我們來看下改進之後的程式碼:
function Promise(executor) { // executor是一個執行函式
let self = this;
self.status = 'pending';
self.value = undefined; // 預設成功的值
self.reason = undefined; // 預設失敗的原因
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) { // 成功狀態
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(reason) { // 失敗狀態
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRjected) {
let self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRjected(self.reason);
}
// 當呼叫then時可能沒成功 也沒失敗
if (self.status === 'pending') {
// 此時沒有resolve 也沒有reject
self.onResolvedCallbacks.push(function () {
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(function () {
onRjected(self.reason);
});
}
}
複製程式碼
下一步,我們就要開始考慮如何支援鏈式呼叫的情況了,如前文所說,鏈式呼叫要求我們每次呼叫then之後都需要返回一個新的Promise物件.
所以在我們需要對onFullfilled和onRejected返回的結果進行判斷分析來決定之後then呼叫時是走向成功還是失敗。返回的值有多種情況,它可能是一個值,也可能是另一個Promise物件。所以讓我們定義一個新的函式用來解析.
function resolvePromise(p2,x,resolve,reject){
// 有可能這裡返回的x是別人的promise
// 儘可能允許其他亂寫
if(p2===x){ //這裡應該報一個型別錯誤,有問題
return reject(new TypeError('迴圈引用了'))
}
// 看x是不是一個promise,promise應該是一個物件
if(x!==null||(typeof x === 'object'||typeof x === 'function')){
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try{ // {then:1}
let then = x.then;
if(typeof then === 'function'){
// 成功
then.call(x,function(y){
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2,y,resolve,reject)
},function(err){ //失敗
reject(err);
})
}else{
resolve(x)
}
}catch(e){
reject(e);
}
}else{ // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
複製程式碼
這裡判斷的邏輯是先判斷返回值x是不是指向當前將會返回的Promise物件自己,如果是自己的話就變成是自己等待自己,那我們就直接丟擲一個迴圈引用的錯誤,然後接著判斷x是不是一個物件,如果不是就說明是普通值,我們直接返回成功resolve並傳入值x,如果是物件的話並且有一個then的方法的話我們就當他是一個Promise物件,如果不是Promise物件的話我們也就直接返回成功resolve並傳入物件x,如果是的話那我們就直接呼叫then方法去判定它的成功與失敗,失敗的話我們就直接呼叫reject,成功的話我們還需在判斷成功的值是否為Promise物件,如果不是的話我們可以直接resolve,是的話則我們要繼續遞迴呼叫resolvePromise方法去解析一直到獲取一個值位置.
我們看一下目前為止的程式碼:
function Promise(executor) { // executor是一個執行函式
let self = this;
self.status = 'pending';
self.value = undefined; // 預設成功的值
self.reason = undefined; // 預設失敗的原因
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) { // 成功狀態
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(reason) { // 失敗狀態
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
try {
executor(resolve, reject)
} catch (e) { // 捕獲的時候發生異常,就直接失敗了
reject(e);
}
}
function resolvePromise(p2,x,resolve,reject){
// 有可能這裡返回的x是別人的promise
// 儘可能允許其他亂寫
if(p2===x){ //這裡應該報一個型別錯誤,有問題
return reject(new TypeError('迴圈引用了'))
}
// 看x是不是一個promise,promise應該是一個物件
if(x!==null||(typeof x === 'object'||typeof x === 'function')){
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try{ // {then:1}
let then = x.then;
if(typeof then === 'function'){
// 成功
then.call(x,function(y){
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2,y,resolve,reject)
},function(err){ //失敗
reject(err);
})
}else{
resolve(x)
}
}catch(e){
reject(e);
}
}else{ // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
Promise.prototype.then = function (onFulfilled, onRjected) {
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
// 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
// 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 = onRjected(self.reason);
resolvePromise(promise2,x,resolve,reject);
})
}
// 當呼叫then時可能沒成功 也沒失敗
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
// 此時沒有resolve 也沒有reject
self.onResolvedCallbacks.push(function () {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
});
self.onRejectedCallbacks.push(function () {
let x = onRjected(self.reason);
resolvePromise(promise2,x,resolve,reject);
});
})
}
return promise2;
}
module.exports = Promise
複製程式碼
下一步,由於規範中要求返回的Promise物件中的onFullfilled和onRejected執行邏輯必須遵從是非同步的,所以我們可以用一個setTimeout函式來包住執行邏輯
setTimeout(function(){
try{
let x = onFulfilled(self.value);
// x可能是別人promise,寫一個方法統一處理
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
})
複製程式碼
同時如果呼叫then時沒有傳入onFullfilled或onRejected的話我們可以定義一個預設的函式給它們,這樣就可以解決到值穿透的問題,例如promise.then().then().then()這樣的呼叫
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:function(value){
return value;
}
onRjected = typeof onRjected === 'function'?onRjected:function(err){
throw err;
}
複製程式碼
另外一個問題就是有些Promise的實現可能可以既呼叫成功又失敗,那麼對於這種情況我們就需要專門處理,永遠只處理第一個呼叫的
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try { // {then:1}
let then = x.then;
if (typeof then === 'function') {
// 成功
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
複製程式碼
以上我們就完成了一個滿足規範的Promise實現了,最後的實現程式碼如下:
function Promise(executor) { // executor是一個執行函式
let self = this;
self.status = 'pending';
self.value = undefined; // 預設成功的值
self.reason = undefined; // 預設失敗的原因
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) { // 成功狀態
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(reason) { // 失敗狀態
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
try {
executor(resolve, reject)
} catch (e) { // 捕獲的時候發生異常,就直接失敗了
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 有可能這裡返回的x是別人的promise
// 儘可能允許其他亂寫
if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
return reject(new TypeError('迴圈引用了'))
}
// 看x是不是一個promise,promise應該是一個物件
let called; // 表示是否呼叫過成功或者失敗
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try { // {then:1}
let then = x.then;
if (typeof then === 'function') {
// 成功
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
Promise.prototype.then = function (onFulfilled, onRjected) {
//成功和失敗預設不穿給一個函式
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
throw err;
}
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
// 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
// x可能是一個promise 也有可能是一個普通的值
setTimeout(function () {
try {
let x = onFulfilled(self.value);
// x可能是別人promise,寫一個方法統一處理
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
// 當呼叫then時可能沒成功 也沒失敗
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
// 此時沒有resolve 也沒有reject
self.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
self.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
module.exports = Promise;
複製程式碼
對於以上程式碼實現,我們可以通過一個promises-aplus-tests的庫來驗證是否達成規範要求
可以通過npm install -g promises-aplus-tests,使用時直接promises-aplus-tests 檔名即可
接下來讓我們來實現一個類似於之前介紹的Q庫中defer的語法糖,首先我們看一下以下這段程式碼:
function read() {
return new Promise(function(resolve,reject) {
require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
if(err) reject(err);
resolve(data);
})
})
}
read().then(function (data) {
console.log(data)
},function(err){
console.log(err);
})
複製程式碼
這裡我們封裝了一個read函式,並返回一個Promise物件,但是可以看到這裡我們必須在非同步邏輯外面包一層Promise.我們可以通過定義Promise.defer的這樣一個語法糖來簡化這一層程式碼.
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
let Promise = require('./Promise');
function read() {
let defer = Promise.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);
})
複製程式碼
同理包括catch,all,race,resolve,reject我們都可以自己實現,這裡就不多加贅述,直接上最終的程式碼:
function Promise(executor) { // executor是一個執行函式
let self = this;
self.status = 'pending';
self.value = undefined; // 預設成功的值
self.reason = undefined; // 預設失敗的原因
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value) { // 成功狀態
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
});
}
}
function reject(reason) { // 失敗狀態
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
try {
executor(resolve, reject)
} catch (e) { // 捕獲的時候發生異常,就直接失敗了
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 有可能這裡返回的x是別人的promise
// 儘可能允許其他亂寫
if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
return reject(new TypeError('迴圈引用了'))
}
// 看x是不是一個promise,promise應該是一個物件
let called; // 表示是否呼叫過成功或者失敗
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try { // {then:1}
let then = x.then;
if (typeof then === 'function') {
// 成功
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
Promise.prototype.then = function (onFulfilled, onRjected) {
//成功和失敗預設不穿給一個函式
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
throw err;
}
let self = this;
let promise2; //返回的promise
if (self.status === 'resolved') {
promise2 = new Promise(function (resolve, reject) {
// 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
// x可能是一個promise 也有可能是一個普通的值
setTimeout(function () {
try {
let x = onFulfilled(self.value);
// x可能是別人promise,寫一個方法統一處理
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
// 當呼叫then時可能沒成功 也沒失敗
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
// 此時沒有resolve 也沒有reject
self.onResolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
self.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRjected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
// 捕獲錯誤的方法
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}
// 解析全部方法
// let arr = [];
// arr[1] = 100;
// console.log(arr.length)
Promise.all = function (promises) {
//promises是一個promise的陣列
return new Promise(function (resolve, reject) {
let arr = []; //arr是最終返回值的結果
let i = 0; // 表示成功了多少次
function processData(index, y) {
arr[index] = y;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y)
}, reject)
}
})
}
// 只要有一個promise成功了 就算成功。如果第一個失敗了就失敗了
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
// 生成一個成功的promise
Promise.resolve = function(value){
return new Promise(function(resolve,reject){
resolve(value);
})
}
// 生成一個失敗的promise
Promise.reject = function(reason){
return new Promise(function(resolve,reject){
reject(reason);
})
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
module.exports = Promise;
複製程式碼