從零實現一個Promise

1、Promise/A+ 規範

① Promise 是一個或者函數內部擁有3個狀態,分別爲pending(等待)fulfilled(執行、完成)rejected(拒絕、未完成)
默認爲pending狀態,即Promise對象剛建立的時候狀態爲pending,而且pending狀態能夠轉換fulfilled或者rejected
fulfilled和rejected爲最終的狀態一旦變爲fulfilled或者rejected,那麼將沒法轉變爲其餘狀態promise

② Promise須要對外提供一個then方法異步

promise.then(onFulfilled, onRejected)

若是可選參數onFulfilled和onRejected不爲函數時應該被忽略函數

onFulfilled和onRejected函數都應該是異步執行的;this

當調用 onFulfilled 函數時,會將當前 Promise 的值做爲參數傳入,而且只能調用一次code

當調用 onRejected 函數時,會將當前 Promise 的失敗緣由做爲參數傳入,而且只能調用一次對象

then函數的返回值仍然爲Promise,以便進行鏈式調用;遞歸

resolvePromise
then方法會建立並返回一個Promise對象,then中註冊的回調函數會返回各類值必須進行校驗回調函數

then方法返回的promise不能與then中回調函數返回值x相等,不然須要拋出錯誤源碼

若是是then回調函數返回值爲一個非Promise對象,則直接用then返回的promise對象的resolve方法,resolve(x)便可。it

若是then回調函數返回值x爲一個Promise對象或者一個帶then方法的對象或函數,那麼須要執行其then方法註冊回調拿到Promise或類Promise對象的值做爲then返回的promise的值,若是值仍然爲Promise對象則須要進行遞歸操做

2、實現Promise

① 根據第一條規範,Promise是一個類或者函數,因此咱們先將Promise定義成一個類,同時內部有三個狀態,咱們將其定義爲常量

var PENDING = "pending"; // 等待狀態
var FULFILLED = "fulfilled"; // 執行、完成狀態
var REJECTED = "rejected"; // 拒絕、未完成狀態
class Promise {
    constructor() {
        this.state = PENDING; // Promise對象建立完成後默認爲等待狀態
    }
}

② 咱們在建立Promise的時候會傳入一個函數,該函數會在建立Promise對象的時候當即執行,而且會接收兩個參數,分別用於執行拒絕當前Promise對象,即修改當前Promise對象的狀態。Promise是用於處理異步的,因此在Promise狀態變爲完成的時候可能會接收到異步操做執行的結果在Promise狀態變爲未完成的時候可能會接收到失敗的緣由,因此Promise內部還須要保存異步操做的結果value失敗的緣由reason

......
class Promise {
    constructor(executor) { // 傳入執行器函數
        ......
        this.value = undefined; // 保存異步操做的結果
        this.reason = undefined; // 保存失敗的緣由
        const resolve = (value) => {
            this.value = value;
            this.state = FULFILLED; // 將Promise對象的狀態改成完成狀態
        }
        const reject = (reason) => {
            this.reason = reason;
            this.state = REJECTED; // 將Promise對象的狀態改成未完成狀態
        }
        try {
            executor(resolve, reject); // 執行器由用戶傳入可能會發生錯誤,因此須要進行捕獲
        } catch(e) {
            reject(e);
        }
    }
}

③ 這裏還存在一個問題,就是Promise必須是單次執行的,Promise的狀態一旦從pending狀態修改成fulfilled或者rejected,就不能再發生變化,從fulfilled變爲fulfilled也不能夠,也就是說resolve或者reject只能執行一次。因此咱們須要對resolve和reject內部進行判斷,若是狀態已經變化了則再也不執行了,如:

......
class Promise {
    constructor(executor) { // 傳入執行器函數
        ......
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用戶屢次resolve,以第一次resolve爲準
                ......
            }
        }
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用戶屢次reject
                ......
            }
        }
        ......
    }
}

④ 給Promise添加一個then函數,then函數接收onFulfilled, onRejected兩個函數做爲參數,分別用於處理Promise完成時和未完成時的回調函數,若是不是函數,則要進行初始化爲一個函數,如:

class Promise {
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 若是onFulfilled不是函數,則初始化一個完成處理函數
            return value;
        };
        onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 若是onRejected不是函數,則初始化一個未完成處理函數
            throw reason; // 傳什麼就拋出什麼
        }
    }
}

⑤ then方法其實就是一個註冊回調的過程,當調用then的這個Promise對象的狀態變爲完成狀態就能夠執行onFulfilled回調函數,當Promise對象的狀態變爲拒絕狀態就能夠執行onRejected回調函數了。因此回調函數的執行依賴於調用then的Promise的狀態。同時爲了支持鏈式調用,then方法還須要返回一個Promise對象。根據前面的Promise規範,傳入的回調函數必須異步執行,這裏用setTimeout進行模擬。

class Promise {
    then(onFulfilled, onRejected) {
        ......
        let promise;
        switch(this.state) {
            case FULFILLED: // 調用then方法的時候,當前Promise狀態已經變成完成狀態,則可用當即執行完成的回調函數
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                        } catch(e) {
                            console.log(e); // 打印錯誤信息
                            reject(e);
                        }
                    });
                });
                break;
            case REJECTED:
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                        } catch(e) {
                            reject(e);
                        }
                    });
                }
                break;
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    // TODO
                });
                break;
        }
        return promise;
    }
}

⑥ 當調用then的Promise對象處於pending狀態的時候,此時經過then註冊的回調函數不能當即執行,必須等待Promise的狀態變爲最終狀態才能執行註冊的回調函數。這裏就涉及到了一個發佈訂閱模式。咱們能夠先將回調函數保存起來,那麼何時Promise纔會變成最終狀態呢?那就是調用resolve或reject的時候,因此咱們能夠在調用resolve或reject的時候,取出註冊的回調函數而後執行便可。

class Promise {
    constructor(executor) {
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用戶屢次resolve,以第一次resolve爲準
                ......
                this.onFulfilleds.forEach(fn => fn()); // 取出then中註冊的完成回調函數並執行
            }
        };
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用戶屢次reject
                ......
                this.onRejecteds.forEach(fn => fn()); // 取出then中註冊的拒絕回調函數並執行
            }
        };
    }
    then(onFulfilled, onRejected) {
        ......
        switch(this.state) {
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    this.onFulfilleds.push(() => {
                        try {
                            let x = onFulfilled(this.value);
                        } catch(e) {
                            console.log(e); // 打印錯誤信息
                            reject(e);
                        }
                    });
                    this.onRejecteds.push(() => {
                        try {
                            let x = onRejected(this.reason);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                break;
        }
    }
}

⑦ 接下來就是要處理then註冊的回調函數的返回值了,由於回調函數的返回值多是各類各樣的狀況,多是普通的值,多是Promise對象,也多是帶then方法的對象,因此咱們要一一進行處理。這裏咱們使用一個單獨的方法resolvePromise()進行各類狀況的處理,如:

// 傳入then()方法中建立的Promise對象,回調函數的返回值x,then()方法中建立的Promise的resolve、reject
const resolvePromise = function(promise, x, resolve, reject) {
    // TODO
}
class Promise {
    constructor(executor) { // 傳入執行器函數
        ......
    }
    then(onFulfilled, onRejected) {
        case FULFILLED:
            promise = new Promise((resolve, reject) => {
                ......
                let x = onFulfilled(this.value);
                resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
            });
        case REJECTED:
            promise = new Promise((resolve, reject) => {
                ......
                let x = onRejected(this.reason);
                resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
            });
        case PENDING:
            this.onFulfilleds.push(() => {
                let x = onFulfilled(this.value);
                resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
            });
            this.onRejecteds.push(() => {
                let x = onRejected(this.reason);
                resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
            });
    }
}

3、實現resolvePromise

① 若是回調函數返回值與then()方法中建立的Promise對象相同則拋出錯誤,這至關因而本身等本身會進入死循環

let p1 = new Promise((resolve, reject) => {
    resolve(1);
})
let p2 = p1.then((value) => { // p2即then方法內建立Promise對象
    return p2;
});
// 結果拋出錯誤,顯示Chaining cycle detected for promise #<Promise>
const resolvePromise = function(promise, x, resolve, reject) {
    if (promise === x) { // 禁止resolve本身
        throw new Error("Chaining cycle detected for promise #<Promise>");
    }
}

② 若是回調函數返回的是一個Promise對象或者帶then方法的類Promise對象,又或者一個函數,由於函數上也可能有then方法,那麼咱們須要取出then方法並執行,對於Promise對象而言,then方法的執行就會註冊相應的回調函數等Promise狀態變爲最終狀態後就會執行對應的回調函數,回調函數執行後就能夠拿到Promise對象的value值,而後將該value值做爲調用then方法建立的Promise的對象的value值

const resolvePromise = function(promise, x, resolve, reject) {
    ......
    if ((x && typeof x === "object") || typeof x === "function") { // 若是是對象或者函數,函數也可能有then方法
        let executed;
        try {
            let then = x.then; // 嘗試取出then方法
            if (typeof then === "function") { // 若是該對象上存在then方法,那麼是個Promise對象或者包含then方法的對象
                then.call(x, function (y) { // 執行then方法,對於真正的Promise對象,則會註冊回調,等到狀態變化後,回調函數會執行,回調中能接收到Promise的value值
                    if (executed) return;
                    executed = true; // 註冊的回調函數只能執行一次
                    resolvePromise(promise, y, resolve, reject); // 返回值還多是一個Promise對象,故須要遞歸直到變爲普通值爲止
                }, function (e) {
                    if (executed) return;
                    executed = true;
                    reject(e);
                });
            } else { // 不包含then方法的普通對象,直接resolve便可
                resolve(x);      
            }
        } catch(e) {
            if (executed) return;
            executed = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

4、實現catch

catch能夠看作是一個特殊的then方法其內部會調用then()方法,可是僅註冊拒絕的回調函數,這也就是then(onFulfilled, onRejected)和then(onFulfilled).catch(onRejected)的區別,若是將onRejected寫到then中,那麼當then的onFulfilled發生錯誤的時候onRejected就沒法捕獲到其中的錯誤,而寫到catch中,那麼就至關因而下一個then()方法,故能捕獲到上一個then()方法中發生的錯誤。

class Promise {
    catch(onRejected) {
        return this.then(null, onRejected); // 僅註冊拒絕的回調函數
    }
}

5、總結

Promise其實就是一個類,內部有state、value、reason等屬性,分別用於存儲當前Promise的狀態執行成功後的返回值執行失敗的緣由,同時內部還提供了resolve、reject兩個方法,這兩個方法會以參數的形式傳遞給執行器,即傳遞到外部,以便修改Promise的狀態。
Promise還提供了一個then方法用於註冊回調函數,註冊回調的時候與當前Promise的狀態有關,若是是最終狀態,則當即執行,若是是等待狀態,則先保存起來等到調用resolve或reject方法的時候再取出回調並執行。註冊的回調函數可能會返回各類各樣的值:
若是返回的是普通值,那麼直接用then返回的Promise的resolve方法resolve便可;
若是返回的是Promise對象或者是帶then方法的對象或函數,那麼須要調用其then方法並註冊一個自定義回調用於接收當前Promise的值,等該Promise變爲最終狀態後會執行回調就能夠拿到其value,最後將其做爲then返回的Promise的value,即resolve(x)。
完整源碼以下:

var PENDING = "pending"; // 等待狀態
var FULFILLED = "fulfilled"; // 執行、完成狀態
var REJECTED = "rejected"; // 拒絕、未完成狀態
// 傳入then()方法中建立的Promise對象,回調函數的返回值x,then()方法中建立的Promise的resolve、reject
const resolvePromise = function(promise, x, resolve, reject) {
    if (promise === x) { // 禁止resolve本身
        throw new Error("Chaining cycle detected for promise #<Promise>");
    }
    if ((x && typeof x === "object") || typeof x === "function") { // 若是是對象或者函數,函數也可能有then方法
        let executed;
        try {
            let then = x.then; // 嘗試取出then方法
            if (typeof then === "function") { // 若是該對象上存在then方法,那麼是個Promise對象或者包含then方法的對象
                then.call(x, function (y) { // 執行then方法,對於真正的Promise對象,則會註冊回調,等到狀態變化後,回調函數會執行,回調中能接收到Promise的value值
                    if (executed) return;
                    executed = true; // 註冊的回調函數只能執行一次
                    resolvePromise(promise, y, resolve, reject); // 返回值還多是一個Promise對象,故須要遞歸直到變爲普通值爲止
                }, function (e) {
                    if (executed) return;
                    executed = true;
                    reject(e);
                });
            } else { // 不包含then方法的普通對象,直接resolve便可
                resolve(x);
            }
        } catch(e) {
            if (executed) return;
            executed = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
class Promise {
    constructor(executor) { // 傳入執行器函數
        this.state = PENDING; // Promise對象建立完成後默認爲等待狀態
        this.value = undefined; // 保存異步操做的結果
        this.reason = undefined; // 保存失敗的緣由
        this.onFulfilleds = []; // 保存then中註冊的完成回調函數
        this.onRejecteds = []; // 保存then中註冊的拒絕回調函數
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用戶屢次resolve,以第一次resolve爲準
                this.value = value;
                this.state = FULFILLED; // 將Promise對象的狀態改成完成狀態
                this.onFulfilleds.forEach(fn => fn()); // 取出then中註冊的完成回調函數並執行
            }
        };
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用戶屢次reject
                this.reason = reason;
                this.state = REJECTED; // 將Promise對象的狀態改成未完成狀態
                this.onRejecteds.forEach(fn => fn()); // 取出then中註冊的拒絕回調函數並執行
            }
        };
        try {
            executor(resolve, reject); // 執行器由用戶傳入可能會發生錯誤,因此須要進行捕獲
        } catch(e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 若是onFulfilled不是函數,則初始化一個完成處理函數
            return value;
        };
        onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 若是onRejected不是函數,則初始化一個未完成處理函數
            throw reason; // 傳什麼就拋出什麼
        }
        let promise;
        switch(this.state) {
            case FULFILLED: // 調用then方法的時候,當前Promise狀態已經變成完成狀態,則可用當即執行完成的回調函數
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            console.log(e);
                            reject(e);
                        }
                    });
                });
                break;
             case REJECTED:
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                break;
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    this.onFulfilleds.push(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                    this.onRejecteds.push(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                break;
            }
        return promise;
    }
    catch(onRejected) {
        return this.then(null, onRejected); // 僅註冊拒絕的回調函數
    }
}
相關文章
相關標籤/搜索