web前端面試題:你能讀懂的Promise源碼實現(手寫代碼)

Promise是web前端工程師在面試的過程中很難繞過的一個坎。如果您目前處於對Promise一知半解,或僅僅是停留在可以使用的層面上,建議您跟着本文敲打練習一遍,相信您一定會有所收穫!另外文章有點長……

一、實現Promise以下功能:
const p1 = new Promise((resolve, reject) => {
    resolve("成功");
})
p1.then(value => {
    // 成功
    console.log(value);
})

const p2 = new Promise((resolve, reject) => {
    reject("失敗");
})
p2.then(undefined, reason => {
    // 失敗
    console.log(reason);
})

const p3 = new Promise((resolve, reject) => {
    throw "異常"
})
p3.then(undefined, reason => {
    // 異常
    console.log(reason);
})
1、根據上面的代碼,我們不難推斷出其基本的結構:
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor) {

}
/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數onResolved爲成功時調用的函數;
* 第二個參數onRejected爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved,onRejected) {

}
2、Promise 對象存在三種狀態:Pending(進行中)resolved(已成功)rejected(已失敗)。我們可以將其設置爲三個常量:
// 進行中狀態
const _PENDING="pending";
// 已成功狀態
const _RESOLVED="resolved";
// 已失敗狀態
const _REJECTED="rejected";
3、我們在實例化Promise時,resolvereject函數將會得到執行,一旦執行其實例的狀態會由pending更改爲resolvedrejected,然後在原型對象方法then當中進行接收。所以我們要幹以下幾個事情:
  • 實例中創建兩個屬性statusvalue
  • 創建內部函數_resolve_reject用於更新statusvalue
  • 立即執行executor函數
代碼如下:
function Promise(executor){
    /****** 1、實例中創建兩個屬性`status`與`value` ******/
    // 設置狀態初始值爲 pending
    this.status = _PENDING;
    // 設置初始值爲 undefined
    this.value = undefined;
   /****** 2、創建內部函數`_resolve`與`_reject`用於更新`status`與`value` ******/
    // 成功時執行
    function _resolve(value) {
        // 修改 promise 對象的狀態爲 resolve
        this.status = _RESOLVED;
        // 保存成功的數據
        this.value = value;
    }
    // 失敗時執行
    function _reject(reason) {
        // 修改 promise 對象的狀態爲 resolve
        this.status = _REJECTED;
        // 保存失敗的數據
        this.value = reason;
    }
    /****** 3、立即執行`executor`函數 ******/
    try{
        // 立即執行 executor
        executor(_resolve.bind(this),_reject.bind(this))
    }catch (err) {
        _reject.call(this,err);
    }
}
4、我們需要在then函數內,根據實例的狀態state來判斷要執行onResolved成功函數,還是onRejected失敗函數。Promise的核心then函數初始代碼如下:
Promise.prototype.then = function (onResolved,onRejected) {
    switch (this.status) {
        // 當狀態爲resolve時,執行onResolved,並傳遞結果
        case _RESOLVED:
            onResolved(this.value);
            break
        // 當狀態爲reject時,執行onRejected,並傳遞結果
        case _REJECTED:
            onRejected(this.value);
            break
    }
}
5、當前已完成代碼如下:
// 進行中狀態
const _PENDING="pending";
// 已成功狀態
const _RESOLVED="resolved";
// 已失敗狀態
const _REJECTED="rejected";
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor){
    // 設置狀態初始值爲 pending
    this.status = _PENDING;
    // 設置初始值爲 undefined
    this.value = undefined;
    // 成功時執行
    function _resolve(value) {
        // 修改 promise 對象的狀態爲 resolve
        this.status = _RESOLVED;
        // 保存成功的數據
        this.value = value;
    }
    // 失敗時執行
    function _reject(reason) {
        // 修改 promise 對象的狀態爲 resolve
        this.status = _REJECTED;
        // 保存失敗的數據
        this.value = reason;
    }
    try{
        // 立即執行 executor
        executor(_resolve.bind(this),_reject.bind(this))
    }catch (err) {
        _reject.call(this,err);
    }
}
/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數onResolved爲成功時調用的函數;
* 第二個參數onRejected爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved,onRejected) {
    switch (this.status) {
        // 當狀態爲resolve時,執行onResolved,並傳遞結果
        case _RESOLVED:
            onResolved(this.value);
            break
        // 當狀態爲reject時,執行onRejected,並傳遞結果
        case _REJECTED:
            onRejected(this.value);
            break
    }
}
二、Promise的狀態state只允許更改一次

Promise即承諾,一旦承諾便會給予結果,且結果是不允許更改的。也就是說狀態state一旦確定便不可更改。

const p1 = new Promise((resolve, reject) => {
    resolve("成功");
    reject("失敗");
})
p1.then(value => {
    // 不會執行
    console.log(value);
},reason=>{
    // 輸出:失敗
    console.log(reason);
})

以上代碼正確的輸出應該是成功,而我們自己封裝的Promise輸出結果卻爲失敗!說明我們的statereject("失敗")進行了二次更改,原因很簡單:我們沒有對當前的狀態進行判斷。
所以我們要對_resolve_reject進行調整,分別在其函數體內增加對當前狀態的判斷,如果不是初始狀態pending則不會繼續更新狀態及數據。
在構造函數Promise中找到_resolve_reject函數,代碼調整如下:

// 成功時執行
function _resolve(value) {
    // 如果爲pending退出函數
    if (this.status !== "pending")
        return;
    // 修改 promise 對象的狀態爲 resolve
    this.status = _RESOLVED;
    // 保存成功的數據
    this.value = value;
}
// 失敗時執行
function _reject(reason) {
    // 如果爲pending退出函數
    if (this.status !== "pending")
        return;
    // 修改 promise 對象的狀態爲 resolve
    this.status = _REJECTED;
    // 保存失敗的數據
    this.value = reason;
}

接下來,再來執行最初的程序,輸出爲成功,說明狀態只允許被更新一次了!

三、then函數是異步的

因爲then函數是異步的,所以在正常情況下,以下代碼的輸出應該爲:1 2 3。但是採用我們自己封裝的Promise,其結果卻爲1 3 2。原因:我們並未對then函數進行異步的處理。

const p = new Promise((resolve, reject) => {
    console.log(1);
    resolve("成功");
})
p.then(value=>{
    console.log(3);
    console.log()
})
console.log(2);

接下來,進入到then函數中。我們只需要將執行回調的代碼用setTimeout進行包裹即可:

Promise.prototype.then = function (onResolved, onRejected) {
    switch (this.status) {
        // 當狀態爲resolve時,執行onResolved,並傳遞結果
        case _RESOLVED:
            // 通過 setTimeout 讓代碼異步執行
            setTimeout(() => {
                onResolved(this.value);
            })
            break
        // 當狀態爲reject時,執行onRejected,並傳遞結果
        case _REJECTED:
            // 通過 setTimeout 讓代碼異步執行
            setTimeout(() => {
                onRejected(this.value);
            })
            break
    }
}

再來執行,結果爲1 2 3。問題順利解決!

四、完成Promise的連綴調用(核心)
const p = new Promise((resolve, reject) => {
    resolve("成功");
})
const result = p.then();
// 輸出status爲 resolved 的 Promise 實例
console.log(result);

const p2 = new Promise((resolve, reject) => {
    resolve("失敗");
})
const result2 = p2.then();
// 輸出status爲 resolved 的 Promise 實例
console.log(result2);

const p3 = new Promise((resolve, reject) => {
    throw "異常"
})
const result3 = p3.then();
// 輸出status爲 rejected 的 Promise 實例
console.log(result3);
1、正常情況下,上方代碼不管成功與失敗,then 函數的返回結果始終應該是一個Promise實例,且其狀態均爲resolved。如果出現異常報錯,則返回的狀態爲rejected,如下:

但是,我們目前的then函數是沒有返回值的,所以我們只能得到一個undefined,並且由於我們未給予then函數相對應的參數(類型爲函數),還給我們飄紅報錯了:Uncaught TypeError: onResolved is not a function

所以接下來,我們要做三件事:1、驗證參數是否爲函數。 2、讓then函數直接返回Promise 3、更改promise的狀態:異常執行reject,其它均執行resolve

  • 驗證參數是否爲函數:
 // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
  • then函數返回promisethen函數完整代碼如下:
Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
        onResolved = typeof onResolved === "function" ? onResolved : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : error => {
            throw error
        };
        switch (this.status) {
            // 當狀態爲resolve時,執行onResolved,並傳遞結果
            case _RESOLVED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    onResolved(this.value);
                });
                break
            // 當狀態爲reject時,執行onRejected,並傳遞結果
            case _REJECTED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    onRejected(this.value);
                });
                break
        }
    })
}
  • 更改Promise的狀態:異常執行reject,其它均執行resolve,並傳值。
Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
        onResolved = typeof onResolved === "function" ? onResolved : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : error => {
            throw error
        };
        switch (this.status) {
            // 當狀態爲resolve時,執行onResolved,並傳遞結果
            case _RESOLVED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    // 增加try方法,如果出現異常,執行reject
                    try {
                        onResolved(this.value);
                        // 無異常執行resolve
                        resolve(this.value);
                    } catch (err) {
                        // 出現異常,執行reject
                        reject(err);
                    }
                });
                break
            // 當狀態爲reject時,執行onRejected,並傳遞結果
            case _REJECTED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    // 增加try方法,如果出現異常,執行reject
                    try {
                        onRejected(this.value);
                        // 無異常執行resolve
                        resolve(this.value);
                    } catch (err) {
                        // 出現異常,執行reject
                        reject(err);
                    }
                });
                break
        }
    })
}

接下來,通過咱們封裝的程序,可以得到準確的數據了:


2、我們知道then在其回調函數中返回非Promise的數據,最終得到的result是一個爲resolved狀態的Promise(成功的狀態),倘若返回的是一個Promise數據,那麼最終得到的便是該Promise的狀態(成功或失敗的狀態),例如:
const p = new Promise((resolve, reject) => {
    resolve("成功");
})
const result = p.then(value=>{
    return "返回字符串"+value;
});
console.log("result",result);

const result2 = p.then(value=>{
    return new Promise((resolve,reject)=>{
        resolve("成功的Promise")
    })
});
console.log("result2",result2);

const result3 = p.then(value=>{
    return new Promise((resolve,reject)=>{
        reject("失敗的Promise")
    })
});
console.log("result3",result3);

應該是這樣的結果:


但是,通過我們自己封裝的Promise得到的結果都是一樣的:

  • 原因:沒有在then函數中判斷onResolvedonRejected返回類型。
  • 解決:判斷onResolvedonRejected的返回結果是否爲Promise,如果是Promise,則將其狀態與then要返回的Promise狀態設爲一致。
Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
        onResolved = typeof onResolved === "function" ? onResolved : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : error => {
            throw error
        };
        switch (this.status) {
            // 當狀態爲resolve時,執行onResolved,並傳遞結果
            case _RESOLVED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    // 增加try方法,如果出現異常,執行reject
                    try {
                        let result = onResolved(this.value);
                        // 判斷返回結果是否爲Promise類型
                        if(result instanceof Promise){
                            // result.then(v => {
                            //     // 成功,修改返回的 Promise 狀態爲成功
                            //     resolve(v);
                            // }, r => {
                            //     // 失敗,修改返回的 Promise 狀態爲失敗
                            //     reject(r);
                            // });

                            // result 是 promise,下面這行代碼是上方代碼的簡寫形式
                            result.then(resolve,reject);
                        }else{
                            // 非Promise類型,將結果直接傳遞過去
                            resolve(result);
                        }
                    } catch (err) {
                        // 出現異常,執行reject
                        reject(err);
                    }
                });
                break
            // 當狀態爲reject時,執行onRejected,並傳遞結果
            case _REJECTED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    // 增加try方法,如果出現異常,執行reject
                    try {
                        let result = onRejected(this.value);
                        // 判斷返回結果是否爲Promise類型
                        if(result instanceof Promise){
                            // result.then(v => {
                            //     // 成功,修改返回的 Promise 狀態爲成功
                            //     resolve(v);
                            // }, r => {
                            //     // 失敗,修改返回的 Promise 狀態爲失敗
                            //     reject(r);
                            // });

                            // result 是 promise,下面這行代碼是上方代碼的簡寫形式
                            result.then(resolve,reject);
                        }else{
                            // 非Promise類型,將結果直接傳遞過去
                            resolve(result);
                        }
                    } catch (err) {
                        // 出現異常,執行reject
                        reject(err);
                    }
                });
                break
        }
    })
}

結果:


效果雖然出來了,但是這樣的代碼確實有些臃腫。所以我們要對其進行優化:不難發現兩個case內的代碼除了回調函數不同,其它都是一樣的,所以我們可以將其進行封裝,並將回調函數作爲參數傳遞。封裝優化後:

Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        /*
        * 參數 cb 的值爲 onResolved 或 onRejected 函數
        *  */
        function _callback(cb) {

            // 增加try方法,如果出現異常,執行reject
            try {
                let result = cb(this.value);
                // 判斷返回結果是否爲Promise類型
                if (result instanceof Promise) {
                    // result 是 promise,下面這行代碼是上方代碼的簡寫形式
                    result.then(resolve, reject);
                } else {
                    // 非Promise類型,將結果直接傳遞過去
                    resolve(result);
                }
            } catch (err) {
                // 出現異常,執行reject
                reject(err);
            }
        }
        // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
        onResolved = typeof onResolved === "function" ? onResolved : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : error => {
            throw error
        };
        switch (this.status) {
            // 當狀態爲resolve時,執行onResolved,並傳遞結果
            case _RESOLVED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    _callback.call(this, onResolved);
                });
                break;
            // 當狀態爲reject時,執行onRejected,並傳遞結果
            case _REJECTED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    _callback.call(this, onRejected);
                });
                break;
        }
    })
}
3、程序寫到這一步其實還隱藏着一個bug,如果我們採用連綴的形式會有問題,代碼如下:
let p1 = new Promise((resolve, reject) => {
    resolve('成功')
}).then(value => {
    console.log("我會輸出")
}).then(value => {
    console.log("我不會輸出")
})

正常來講,兩個then均會輸出纔對。而通過我們封裝的Promise,只會將第一個輸出。

  • 分析:因爲我們現在的Promise是同步任務,所以當我們執行到第一個then的時候,當前Promise的狀態已經確定爲resolved。而當執行到第二個then的時候,此時的Promise是通過第一個then得到的,又因爲在第一個then當中有setTimeout,使其變爲了異步,所以會造成resolvereject不會立即調用,最終導致在執行第二個then時,當前Promisestatuspending。也就是說我們更改狀態後,回調方法沒有得到執行。如果此時我們將封裝then函數當中的setTimeout移除掉,則會恢復正常,但將其移除掉封裝也就失去了意義。
  • 解決:我們已經知道原因是當Promise的狀態發生變化時,then函數的回調沒有得到調用。所以我們需要在改變狀態後調用即可。可狀態更改完成之後我們又如何纔可以執行回調?在這個時候我們可以在實例當中創建一個屬性onCallBacks用於存放回調函數隊列,然後在執行then函數時判斷當前狀態如果爲pending則說明爲異步任務,只需將回調函數放置到onCallBacks 屬性中。這樣當異步修改完狀態後,我們就可以通過onCallBacks執行回調了。代碼:
  • 在實例當中創建一個屬性onCallBacks用於存放回調函數隊列。
// 添加回調函數隊列
this.onCallBacks = [];

*在then 函數中判斷當前狀態爲pending 時,將回調函數放置到 onCallBacks數組中。

// 當狀態爲 pending 時,將要執行的回調函數放置到隊列中,待狀態更改完畢後再調用。
case _PENDING:
    this.onCallBacks.push({
        onResolved() {
            //獲取回調函數的執行結果
            _callback.call(this,onResolved);
        },
        onRejected() {
            _callback.call(this,onRejected);
        }
    });
    break;
  • 當異步修改完狀態後,我們就可以通過onCallBacks 執行回調了。
// 成功時執行
function _resolve(value) {
    if (this.status !== "pending")
        return;

    // 修改 promise 對象的狀態爲 resolve
    this.status = _RESOLVED;
    // 保存成功的數據
    this.value = value;
    //檢查回調數組中是否存在數據
    if (this.onCallBacks.length > 0) {
        // 異步執行
        setTimeout(() => {
            this.onCallBacks.forEach(onCb => {
                onCb.onResolved.call(this);
            });
        });
    }
}
// 失敗時執行
function _reject(reason) {
    if (this.status !== "pending")
        return;
    // 修改 promise 對象的狀態爲 resolve
    this.status = _REJECTED;
    // 保存失敗的數據
    this.value = reason;
    //檢查回調數組中是否存在數據
    if (this.onCallBacks.length > 0) {
        // 異步執行
        setTimeout(() => {
            this.onCallBacks.forEach(onCb => {
                onCb.onRejected.call(this);
            });
        });
    }
}

Promise完整代碼如下:

// 進行中狀態
const _PENDING = "pending";
// 已成功狀態
const _RESOLVED = "resolved";
// 已失敗狀態
const _REJECTED = "rejected";
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor) {
    // 設置狀態初始值爲 pending
    this.status = _PENDING;
    // 設置初始值爲 undefined
    this.value = undefined;
    // 添加回調函數隊列
    this.onCallBacks = [];
    // 成功時執行
    function _resolve(value) {
        if (this.status !== "pending")
            return;

        // 修改 promise 對象的狀態爲 resolve
        this.status = _RESOLVED;
        // 保存成功的數據
        this.value = value;
        //檢查回調數組中是否存在數據
        if (this.onCallBacks.length > 0) {
            // 異步執行
            setTimeout(() => {
                this.onCallBacks.forEach(onCb => {
                    onCb.onResolved.call(this);
                });
            });
        }
    }
    // 失敗時執行
    function _reject(reason) {
        if (this.status !== "pending")
            return;
        // 修改 promise 對象的狀態爲 resolve
        this.status = _REJECTED;
        // 保存失敗的數據
        this.value = reason;
        //檢查回調數組中是否存在數據
        if (this.onCallBacks.length > 0) {
            // 異步執行
            setTimeout(() => {
                this.onCallBacks.forEach(onCb => {
                    onCb.onRejected.call(this);
                });
            });
        }
    }
    try {
        // 立即執行 executor
        executor(_resolve.bind(this), _reject.bind(this))
    } catch (err) {
        _reject.call(this, err);
    }
}

/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數 onResolved 爲成功時調用的函數;
* 第二個參數 onRejected 爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        /*
        * 參數 cb 的值爲 onResolved 或 onRejected 函數
        *  */
        function _callback(cb) {
            // 增加try方法,如果出現異常,執行reject
            try {
                let result = cb(this.value);
                // 判斷返回結果是否爲Promise類型
                if (result instanceof Promise) {
                    // result 是 promise,下面這行代碼是上方代碼的簡寫形式
                    result.then(resolve, reject);
                } else {
                    // 非Promise類型,將結果直接傳遞過去
                    resolve(result);
                }
            } catch (err) {
                // 出現異常,執行reject
                reject(err);
            }
        }

        // 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
        onResolved = typeof onResolved === "function" ? onResolved : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : error => {
            throw error
        };
        switch (this.status) {
            // 當狀態爲resolve時,執行onResolved,並傳遞結果
            case _RESOLVED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    _callback.call(this, onResolved);
                });
                break;
            // 當狀態爲reject時,執行onRejected,並傳遞結果
            case _REJECTED:
                // 通過 setTimeout 讓代碼異步執行
                setTimeout(() => {
                    _callback.call(this, onRejected);
                });
                break;
            // 當狀態爲 pending 時,將要執行的回調函數放置到隊列中,待狀態更改完畢後再調用。
            case _PENDING:
                this.onCallBacks.push({
                    onResolved() {
                        //獲取回調函數的執行結果
                        _callback.call(this,onResolved);
                    },
                    onRejected() {
                        _callback.call(this,onRejected);
                    }
                });
                break;
        }
    })
}

截止到目前,我們已經完成了Promise的鏈式調用。

五、其它API
// catch方法的封裝
Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected);
}
// 函數對象 resolve 的封裝
Promise.resolve = function (value) {
    return new Promise((resolve, reject) => {
        if (value instanceof Promise) {
            value.then(resolve, reject);
        } else {
            resolve(value);
        }
    });
}
// 函數對象 reject 的封裝
Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
        reject(reason);
    })
}
//函數對象 all 的封裝
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let pValues = [];
        let flag = 0;
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(v => {
                pValues[i] = v;
                flag++;
                if (flag >= promises.length) {
                    resolve(pValues);
                }
            }, r => {
                reject(r);
            })
        }
    });
}
// 函數對象 race
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for(let i=0;i<promises.length;i++){
            promises[i].then(value=>{
                resolve(value);
            }, reason=>{
                reject(reason);
            })
        }
    });
}

—————END—————
喜歡本文的朋友們,歡迎關注公衆號 張培躍,收看更多精彩內容!!

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