Promise 簡單實現

Promise 簡單實現

前言

你可能知道,javascript 的任務執行的模式有兩種:同步和異步。

異步模式非常重要,在瀏覽器端,耗時很長的操作(例如 ajax 請求)都應該異步執行,避免瀏覽器失去響應。

在異步模式編程中,我們經常使用回調函數。一不小心就可能寫出以下這樣的代碼:

//事件1
doSomeThing1(function() {
    //事件2
    doSomeThing2(function() {
        //事件3
        doSomeThing3();
    });
});

當你的需要異步執行的函數越來越多,你的層級也會越來越深。

這樣的寫法存在的缺點是:

  1. 不利於閱讀
  2. 各個任務之間的高度耦合,難以維護
  3. 對異常的處理比較難

用 Promise 可以避免這種回調地獄,可以寫成這樣

//事件1
doSomeThing1()
    .then(function() {
        //事件2
        return doSomeThing2();
    })
    .then(function() {
        //事件3
        return doSomeThing3();
    })
    .catch(function() {
        //這裏可以很方便的做異常處理
    });

在市面上有許多庫都實現了 Promise,例如:Q.js 、when.js ,es6 也將 Promise 納入了標準中

es6 的 Promise 使用方法可以參考阮一峯的 http://es6.ruanyifeng.com/#do... ,我就不在做具體介紹

接下來,我們模仿 ES6 的 promise,一步一步來實現一個簡單版的 Promise。

構造函數

我們使用 Promise 的時候,

const promise = new Promise((resolve, reject)=>{
  // ... some code
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 是一個構造函數,接收一個函數,函數裏有兩個參數,resolve、reject。

我們可以這樣子實現:

class PromiseA {
    constructor(executor) {
        const resolve = value => {
            this.resolve(value);
        };
        const reject = err => {
            this.reject(err);
        };
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }

    resolve(value) {
        this.resultValue = value;
    }

    reject(error) {
        this.resultValue = error;
    }
}

then 方法

promise 中,用的最頻繁的就是 then 方法,then 方法有兩個參數,一個是 promise 成功時候的回調,一個是失敗的回調。

實現方式爲:

class PromiseA {
    constructor(executor) {
        const resolve = value => {
            this.resolve(value);
        };
        const reject = err => {
            this.reject(err);
        };
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }

    resolve(value) {
        this.resultValue = value;
        if (this.fullfillCallback) {
            //++++++++
            this.fullfillCallback(value);
        }
    }

    reject(error) {
        this.resultValue = error;
        if (this.rejectCallback) {
            //++++++++
            this.rejectCallback(value);
        }
    }

    then(resolve, reject) {
        this.fullfillCallback = resolve;
        this.rejectCallback = resolve;
    }
}

then 方法有以下幾個特性:

  1. 支持鏈式操作
  2. 每次 then 方法都是返回新的 Promise
  3. 當前 promise 的狀態通過返回值傳遞給下一個 promise
  4. 錯誤冒泡,即如果當前 promise 沒有提供 onReject 方法,會把錯誤冒泡到下一個 promise,方便處理
then(onResolve,onReject){

  //返回新的Promise並支持鏈式操作
  return new PromiseA((resolve,reject)=>{

    this.fullfillCallback = (value)=>{
        try {
            if (onResolve) {
                let newValue = onResolve(value);
                resolve(newValue); //將當前promise執行結果,傳遞給下一個promise
            } else {
                resolve(value);
            }
        } catch (err) {
            reject(err);
        }
    }

    //類似fullfillCallback
    this.rejectCallback = (value)=>{
          try {
              if (onReject) {
                  let newValue = onReject(value);
                  resolve(newValue);
              } else {
                  //錯誤冒泡
                  reject(value);
              }
          } catch (err) {
              reject(err);
         }
    }
  });
}

這樣我們就實現了一個簡單版的 then 方法了

加強版 then

上面的實現,模擬了 then 方法的邏輯,但是還有一些缺點:

  1. then 方法只能添加一個,例如
let p = new PromiseA(resolve => {
    setTimeout(() => {
        resolve(1);
    }, 0);
});
p.then(value => {
    console.log('then-->' + value);
}); //無輸出,因爲沒觸發到,被後一個覆蓋了
p.then(value => {
    console.log('then2-->' + value);
}); ////then---> 1
  1. promise 沒有狀態,當 promsie 在添加 then 的時候已經完成了,沒法得到結果
  2. 沒有實現:如果上一個 promise 的返回值也是一個 Promise 對象時,則會等到這個 Promise resolve 的時候才執行下一個

爲了解決第一點,引入了事件監聽,簡單的實現如下:

export default class EventEmitter {
    constructor() {
        this._events = {};
    }

    on(type, fn, context = this) {
        if (!this._events[type]) {
            this._events[type] = [];
        }
        this._events[type].push([fn, context]);
    }

    trigger(type) {
        let events = this._events[type];
        if (!events) {
            return;
        }
        let len = events.length;
        let eventsCopy = [...events];
        for (let i = 0; i < len; i++) {
            let event = eventsCopy[i];
            let [fn, context] = event;
            if (fn) {
                fn.apply(context, [].slice.call(arguments, 1));
            }
        }
    }
}

所以進一步對 PromiseA 進行改造:

const STATUS = {
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
};

const EventType = {
    fulfill: 'fulfill',
    reject: 'reject'
};

class PromiseA {
    constructor(executor) {
        //初始化事件監聽及狀態
        this.eventEmitter = new EventEmitter();
        this.status = STATUS.PENDING;

        const resolve = value => {
            if (value instanceof PromiseA) {
                value.then(
                    value => {
                        this.resolve(value);
                    },
                    error => {
                        this.reject(error);
                    }
                );
            } else {
                this.resolve(value);
            }
        };
        const reject = err => {
            this.reject(err);
        };
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }

    resolve(value) {
        //增加狀態
        if (this.status === STATUS.PENDING) {
            this.status = STATUS.FULFILLED;
            this.resultValue = value;
            this.eventEmitter.trigger(EventType.fulfill, value);
        }
    }

    reject(error) {
        //增加狀態
        if (this.status === STATUS.PENDING) {
            this.status = STATUS.REJECTED;
            this.resultValue = error;
            this.eventEmitter.trigger(EventType.reject, error);
        }
    }

    then(onResolve, onReject) {
        //根據狀態不同處理
        if (this.status === STATUS.PENDING) {
            return new PromiseA((resolve, reject) => {
                //增加事件監聽
                this.eventEmitter.on('fulfill', value => {
                    try {
                        if (onResolve) {
                            let newValue = onResolve(value);
                            resolve(newValue);
                        } else {
                            resolve(value);
                        }
                    } catch (err) {
                        reject(err);
                    }
                });
                //增加事件監聽
                this.eventEmitter.on('reject', value => {
                    try {
                        if (onReject) {
                            let newValue = onReject(value);
                            resolve(newValue);
                        } else {
                            reject(value);
                        }
                    } catch (err) {
                        reject(err);
                    }
                });
            });
        }
        if (
            this.status === STATUS.FULFILLED ||
            this.status === STATUS.REJECTED
        ) {
            return new PromiseA((resolve, reject) => {
                let callback = returnValue;
                if (this.status === STATUS.FULFILLED) {
                    callback = onResolve;
                }
                if (this.status === STATUS.REJECTED) {
                    callback = onReject;
                }
                try {
                    let newValue = callback(this.resultValue);
                    resolveValue(newValue, resolve, reject);
                } catch (err) {
                    reject(err);
                }
            });
        }
    }
}

到這裏,我們的 then 方法基本就完成了。

最後還有一個小知識點,就是執行時機的問題:

setTimeout(function() {
    console.log(4);
}, 0);
new Promise(function(resolve) {
    console.log(1);
    resolve();
}).then(function() {
    console.log(3);
});
console.log(2);
//輸出結果會是: 1、2、3、4
promise.then,是異步的,屬於 microtask,執行時機是本次事件循環結束之前,而 setTimeout 是 macrotask,執行時機是在下一次事件循環的開始之時

實現這個功能,我利用了第三方庫 microtask 來模擬。所以 PromiseA 修改爲:

    resolve(value) {
        microtask(() => {
            if (this.status === STATUS.PENDING) {
                this.status = STATUS.FULFILLED;
                this.resultValue = value;
                this.eventEmitter.trigger(EventType.fulfill, value);
            }
        })
    }

    reject(error) {
        microtask(() => {
            if (this.status === STATUS.PENDING) {
                this.status = STATUS.REJECTED;
                this.resultValue = error;
                this.eventEmitter.trigger(EventType.reject, error);
            }
        });
    }

到此爲止,我們的 then 方法已經大功告成了。最困難的一步已經解決了

catch

Promise 跟普通回調的一大優勢就是異常處理,我們推薦使用 Promise 的時候,總是使用 catch 來代替 then 的第二個參數。即是:

//bad
let p = new Promise((resolve, reject) => {
    //...異步操作
}).then(
    value => {
        //成功
    },
    () => {
        //失敗
    }
);

//good
let p = new Promise((resolve, reject) => {
    //...異步操作
})
    .then(value => {
        //成功
    })
    .catch(() => {
        //失敗
    });

接下來讓我們實現 catch 方法:

catch(reject) {
   return this.then(null, reject);
}

哈哈~ , 你沒看錯。你已經實現了 catch 方法

all 方法

Promise.all 是一個很好用的方法。接受一個 promise 數組,然後等到所有的異步操作都完成了,就返回一個數組,包含對應的值

具體實現如下:

static all(promiseList = []) {
      //返回promise以便鏈式操作
    return new PromiseA((resolve, reject) => {
        let results = [];
          let len = promiseList.length;
        let resolveCount = 0; //用於計數

        let resolver = function (index, value) {
            resolveCount++;
            results[index] = value;
            if (resolveCount === len) {
                resolve(results);
            }
        };

          //遍歷執行所有的promise
        promiseList.forEach((p, i) => {
            if (p instanceof PromiseA) {
                p.then((value) => {
                    resolver(i, value);
                }, (err) => {
                    reject(err);
                })
            } else {
                resolver(i, p);
            }
        })
    });
}

race 方法

race 方法爲競速,第一執行完的爲準。所以只需循環一遍執行就可以了。

當有第一個將 Promise 的狀態改變成 fullfilled 或 reject 之後,其他的就都無效了

static race(promiseList = []) {
   return new PromiseA((resolve, reject) => {
      promiseList.forEach((p, i) => {
         if (p instanceof PromiseA) {
            p.then((value) => {
               resolve(value);
            }, (err) => {
               reject(err);
            })
         } else {
            resolve(p);
         }
      })
   })
}

小結

我們實現了一個簡單版的 promise, 還有一些其他的方法在這裏沒有講到。感興趣的朋友可以自行去研究哈~

附上代碼完整的實現 : https://github.com/chen434202...

個人博客鏈接:https://chen4342024.github.io...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章