實現一個符合 Promise/A+ 規範的 MyPromise

PlayerWho發表於2018-01-15

Promise

實現一個符合 Promise/A+ 規範的 MyPromise,並實現 resolve、reject、all、race、defer、deferred等靜態方法。

MyPromise

  1. 作用:建立 MyPromise例項。Promise
  2. MyPromise接收一個回掉函式 executor
  3. MyPromise狀態
    • pending
      • 可以轉換成 fulfilled 或 rejected
    • fulfilled
      • 不可改變成其他狀態
    • rejected
      • 不可改變成其他狀態
  4. onFulfilledCallbacksonRejectedCallbacks
    • 兩個陣列,陣列每一項是一個函式。分別接收then裡面的第一個引數和第二個引數。
    • 狀態是 pending 的回掉函式。
  5. resolve
    • promise的狀態是fulfilled異常是的處理函式
    • 接收 value 引數
      • 如果是promise,執行then
      • 如果不是promise,把value做為引數傳給onFulfilledCallbacks裡的每個函式。
  6. reject
    • promise的狀態是rejected異常是的處理函式
    • 接收 reason 引數,把reason做為引數傳給onRejectedCallbacks裡的每個函式。
  7. 執行 executor,如果有異常,拋給reject
  8. 因為Promise是在同步程式碼執行完成後再執行,所以要把Mypromise的執行方法resolvereject放在非同步佇列裡
function MyPromise(executor) {
    if (typeof executor !== 'function') {
        throw new TypeError('Promise resolver ' + executor + ' is not a function');
    }
    let self = this;
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === 'pending') {
            self.value = value;
            self.status = 'fulfilled';
            self.onFulfilledCallbacks.forEach(item => item(value));
        }
    }
    function reject(reason) {
        if (self.status === 'pending') {
            self.reason = reason;
            self.status = 'rejected';  
            self.onRejectedCallbacks.forEach(item => item(reason));
        }
    }
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

複製程式碼

MyPromise.prototype.then

  1. 作用:接收兩個函式引數,第一個函式的引數是 resolve傳入的引數,第二個引數是 reject傳入的引數。Promise#then
  2. onFulfilled
    • MyPromise 成功時執行的方法
    • resolve 的引數會作為value傳給 onFulfilled
  3. onRejected
    • MyPromise 失敗時執行的方法
    • reject 的引數會作為value傳給 onRejected
  4. 返回一個 MyPromise 例項 newPromise,方便鏈式呼叫
  5. 對三種狀態分別處理
    • 每個狀態中建立 newPromise
    • fulfilled
      • 直接執行 onFulfilled,返回值x
      • newPromisex以及newPromise裡的resolvereject做為引數傳給 resolutionPromise
      • 把 MyPromise 的引數放在非同步佇列裡
    • rejected
      • 直接執行 onRejected,返回值x
      • newPromisex以及newPromise裡的resolvereject做為引數傳給 resolutionPromise
      • 把 MyPromise 的引數放在非同步佇列裡
    • pending
      • 狀態待定,把fulfilledrejected裡的非同步函式分別加到 onFulfilledCallbacksonRejectedCallbacks的最後一位
  6. resolutionPromise 後面細說
  7. catch捕獲異常,執行 reject
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    typeof onFulfilled !== 'function' && (onFulfilled = function (value) {
        return value;
    });
    typeof onRejected !== 'function' && (onRejected = function (reason) {
        throw reason;
    });
    let newPromise;
    /**
     *  分別處理例項的三種狀態
     */
    if (self.status === 'fulfilled') {
        newPromise = new MyPromise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolutionPromise(newPromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === 'rejected') {
        newPromise = new MyPromise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRejected(self.reason);
                    resolutionPromise(newPromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === 'pending') {
        newPromise = new MyPromise(function (resolve, reject) {
            self.onFulfilledCallbacks.push(function (value) {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(value);
                        resolutionPromise(newPromise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
            self.onRejectedCallbacks.push(function (reason) {
                setTimeout(function () {
                    try {
                        let x = onRejected(reason);
                        resolutionPromise(newPromise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        });
    }
    return newPromise;
};

複製程式碼

MyPromise.prototype.catch

  1. 作用:捕獲異常
  2. 返回 MyPromise
MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected);
};
複製程式碼

The Promise Resolution Procedure

  1. Promise解析過程,是以一個 promise、一個值 xresolve, reject做為引數的抽象過程
  2. promise 等於 xreject 丟擲異常 new TypeError('迴圈引用')
  3. x如果不是物件(不包括 null)或者函式,執行 resolve(x)
  4. 獲取 x.then 賦值給 then
    • then 如果是 function
      • x做為 this 呼叫then,第一個引數是 resolvePromise,第二個引數是 rejectPromise
      • resolvePromiserejectPromise只有第一次呼叫有效
      • resolvePromise引數為 y,執行 resolutionPromise(promise, y, resolve, reject)
      • rejectPromise引數為 r,執行 reject(r)
    • then 如果不是 function
      • 執行 resolve(x)
  5. 用捕獲上一步的異常
    • 執行 reject(e)
    • 如果執行過 resolvePromiserejectPromise,忽略
function resolutionPromise(promise, x, resolve, reject) {
    if (promise === x) {
        reject(new TypeError('迴圈引用'));
    }
    let then, called;
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called)
                        return;
                    called = true;
                    resolutionPromise(promise, y, resolve, reject);
                }, function (r) {
                    if (called)
                        return;
                    called = true;
                    reject(r);
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called)
                return;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
複製程式碼

MyPromise 靜態方法

MyPromise.all

MyPromise.all = function (promises) {
    let called = false;
    return new MyPromise(function (resolve, reject) {
        let newArr = [], count = 0;
        for (let i = 0; i < promises.length; i++) {
            let item = promises[i];
            if (!(item instanceof MyPromise)) {
                item = MyPromise.resolve(item);
            }
            item.then(function (data) {
                if (!called) {
                    newArr[i] = data;
                    if (i == count) {
                        resolve(newArr);
                        count++;
                    }
                }
            }, function (e) {
                if (!called) {
                    reject(e);
                    called = true;
                }
            });
        }
    });
};
複製程式碼

MyPromise.race

MyPromise.race = function (promises) {
    return new MyPromise(function (resolve, reject) {
        let called = false;
        for (let i = 0; i < promises.length; i++) {
            let item = promises[i];
            if (!(item instanceof MyPromise)) {
                item = MyPromise.resolve(item);
            }
            item.then(function (data) {
                if (!called) {
                    resolve(data);
                    called = true;
                }
            }, function (e) {
                if (!called) {
                    reject(e);
                    called = true;
                }
            });
        }
    })
};
複製程式碼

MyPromise.resolve

MyPromise.resolve = function (value) {
    if (value instanceof MyPromise) {
        return value;
    }
    return new MyPromise(function (resolve, reject) {
        if (typeof value !== null && typeof value === 'object' && typeof value.then === 'function') {
            value.then();
        } else {
            resolve(value);
        }
    })
};
複製程式碼

MyPromise.reject

MyPromise.reject = function (e) {
    return new MyPromise(function (resolve, reject) {
        reject(e);
    })
};
複製程式碼

test

  • npm i -g promises-aplus-tests
  • promises-aplus-tests Promise.js

原始碼

參考資料

相關文章