1. 程式人生 > 其它 >手寫Promise原始碼實現

手寫Promise原始碼實現

技術標籤:javascript

我們根據Promise的使用方式,實現一個自己的Promise,以此來了解Promise的原始碼思想。

下面的程式碼是一個簡單的Promise的使用案例,我們基於這個案例來實現一個自己的Promise。

首先建立一個Promise物件,在接收的函式中同時呼叫失敗和成功,然後再then方法中列印傳入的引數。

let p = new Promise(funcion(resolve, reject) {
    reject('失敗啦');
    resolve('成功啦');
});

p.then(function(value) {
    console.
log(value); })

這裡需要先定義一個Promise的建構函式。

function Promise() {

}

在建立Promise物件的時候會傳遞一個函式executor,這個函式會立即被呼叫,所以我們在Promise內部立即執行這個函式。

function Promise (executor) {
    excutor();
}

executor在執行的時候會傳入兩個方法,一個是resolve,一個reject,所以我們要建立這兩個函式,而且需要把這兩個函式傳遞給executor。

function Promise (executor) {
    function resolve
() { } function reject() { } excutor(resolve, reject); }

我們在呼叫resolve和reject的時候會傳入一個資訊,成功的時候傳遞一個成功的值,失敗的時候傳遞一個失敗的原因。

function Promise (executor) {
    function resolve(value) {

    }
    function reject(reason) {

    }
    excutor(resolve, reject);
}

Promise的物件存在一個then方法,這個then方法裡面會有兩個引數,一個是成功的回撥onFulfilled,另一個是失敗的回撥onRejected,只要我們呼叫了resolve就會執行onFulfilled,呼叫了reject就會執行onRejected。

Primsie.prototype.then = function(onFulfilled, onRejected) {

}

Promise本身是存在狀態的,預設是等待狀態pending,呼叫了resolve會變成成功狀態,呼叫了reject會變為失敗狀態,所以我們需要在Promise內部定義一個狀態表示當前Promise的狀態。

為了保證this不錯亂,我們定義一個self儲存this。當我們呼叫了resolve或reject的時候,需要讓狀態發生改變.需要注意的是Promise的狀態只可改變一次,所以我們要判斷,只有當狀態未發生改變時,才去改變狀態。

function Promise (executor) {
    var self = this;

    self.status = 'pending';
    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
        }
        
    }
    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
        }
    }
    excutor(resolve, reject);
}

接著呼叫成功resolve或失敗reject的時候傳遞的值我們需要儲存下來,這在後面的then中的方法中還需要使用到這個值。

function Promise (executor) {
    var self = this;

    self.status = 'pending';
    self.value;
    self.reason;

    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;
        }
    }
    excutor(resolve, reject);
}

接著我們在then方法中判斷,當狀態成功的時候執行onFulfilled,當狀態失敗的時候執行onRejected。並且傳入相應的值。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }

    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
}

至此我們就實現了一個簡單的Promise程式碼。Promise的概念不是憑空出現的,是在Promise A+規範(https://promisesaplus.com/)中定義的,這個規範是所有人想實現Promise都必須要基於這個規範。

我們自己寫的Promise可能會和別人的Promise用在一起,為了保證相容性,所有人寫的Promise都要符合這個規範。

規範

1.1 “promise”是一個具有then方法的物件或函式,其行為符合此規範。也就是說Promise是一個物件或者函式。

1.2 “thenable”是一個定義then方法的物件或函式,說句人話也就是這個物件必須要擁有then方法。

1.3 “value”是任何合法的JavaScript值(包括undefined、或者promise)。

1.4 promise中的異常需要使用throw語句丟擲。

1.5 當promise失敗的時候需要給出失敗的原因。

狀態

1.1 promise必須要擁有三個狀態: pending, fulfilled 和 rejected。

1.2 當promise的狀態是pending時,他可以變為成功fulfilled或者失敗rejected。

1.3 如果promise是成功狀態,則他不能轉換成任何狀態,而且需要一個成功的值,並且這個值不能被改變。

1.4 如果promise是失敗狀態,則他不能轉換成任何狀態,而且需要一個失敗的原因,並且這個值不能被改變。

then方法說明

1.1 一個promise必須要有一個then方法,而且可以訪問promise最終的結果,成功或者失敗的值。

1.2 then方法需要接收兩個引數,onFulfilled 和 onRejected這兩個引數是可選引數。

以上我們的程式碼是符合這些規範的,我們繼續向下寫。

現在我們的promise只是實現了第一版最簡易的功能,但是他存在一個非同步邏輯的問題,當我們在Promise函式中非同步呼叫resolve的時候,then方法不會執行。因為then方法執行的時候resolve並沒有執行,也就是promise的狀態還未變化。等到非同步的Promise狀態發生改變的時候,我們的then已經執行完了。而官方的Promise無論then方法是否執行完畢,只要Promise狀態變了,then中繫結的函式就會執行。所以接下來我們要解決這個問題。

下面的例子是官方Promise的應用,同一個例項繫結多個then方法,所有的then繫結的成功或失敗都會相應的執行。

let p = new Promise(funcion(resolve, reject) {
    setTimeout(function() {
        resolve('成功啦');
    }, 1000)    
});

p.then(function(value) {
    console.log(value);
})

p.then(function(value) {
    console.log(value);
})

p.then(function(value) {
    console.log(value);
})

這裡我們就需要改造我們的Promise程式碼。當我們呼叫then方法的時候可能還是pending狀態,這個時候我們應該把onFulfilled和onRejected先存起來,當執行了resolve或者reject的時候再執行onFulfilled或onRejected。

所以我們需要定義兩個變數,分別儲存onFulfilled和onRejected。

function Promise (executor) {
    var self = this;

    self.status = 'pending';
    self.value;
    self.reason;
    self.onResolvedCallbacks = []; // 存放所有成功的回撥。
    self.onRejectedCallbacks = []; // 存放所有失敗的回撥。
    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;
        }
    }
    excutor(resolve, reject);
}

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }

    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
    if (self.status === 'pending') {
        self.onResolvedCallbacks.push(onFulfilled);
        self.onRejectedCallbacks.push(onRejected);
    }
}

因為onFulfilled和onRejected在執行的時候需要傳入對應的value值,所我們這裡用一個函式包裹起來,將對應的值也傳入進去。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }

    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
    if (self.status === 'pending') {
        self.onResolvedCallbacks.push(function () {
                onFulfilled(self.value);
            });
            self.onRejectedCallbacks.push(function() {
                onRejected(self.reason);
            });
    }
}

當我們成功或者失敗的時候,執行onFulfilled和onRejected的函式,也就是在resolve函式中和reject函式中分別迴圈執行對應的陣列中的函式。

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();
        })
    }
}

這個時候當我們非同步執行resolve方法時候,then中繫結的函式就會執行,並且繫結多個then的時候,多個方法都會執行。

鏈式呼叫

Promise最大的優點就是鏈式呼叫,如果一個then方法返回一個普通值,這個值會傳遞給下一次then中,作為成功的結果。如果返回的是一個primise, 則會把promise的執行結果傳遞下去取決於這個promise的成功或失敗。如果返回的是一個報錯就會執行到下一個then的失敗的函式中。

捕獲錯誤的機制是,預設會找距離自己最近的then的失敗方法,如果找不到就向下繼續找,一直找到catch方法。

let p = new Promise(funcion(resolve, reject) {
    setTimeout(function() {
        resolve('成功啦');
    }, 1000)    
});

p.then(function(value) {
    retrun 123;
}).then(function(value) {
    console.log(value);
}).catch(function(error) {
    console.log(error);
}).then(function(data) {
    console.log(data);
})

Promise呼叫then後會返回一個新的Promise,因為Promise的狀態只能改變一次,如果使用同一個Promise的話後面的then就失去了成功失敗的自由性。

所以我們需要在then方法之後再去return一個new Promise, 原本的邏輯放在新建立的Promise內部即可,因為他是立即執行的一個函式。我們這裡定義一個promise2接收新建立的Promise,在函式底部返回出去。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    const promise2 = new Promise(function (resolve, reject) {
        if (self.status === 'resolved') {
            onFulfilled(self.value);
        }

        if (self.status === 'rejected') {
            onRejected(self.reason);
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(function () {
                onFulfilled(self.value);
            });
            self.onRejectedCallbacks.push(function() {
                onRejected(self.reason);
            });
        }
    })
    return promise2;
}

我們需要拿到當前then方法執行成功或失敗的結果,前一個then方法的返回值會傳遞給下一個then方法,所以這裡我們要關心onFulfilled(self.value) 和 onRejected(self.reason)的返回值,我們這裡定義一個x來接收一下。

...
const x = onFulfilled(self.value);
...
const x = onRejected(self.reason);
...

如果x是一個普通值的話,我們可以直接呼叫promise2的resolve方法,將這個值傳遞出去即可,這樣下一個then就可以獲取的到,所以我們執行resolve(x)即可。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    const promise2 = new Promise(function (resolve, reject) {
        if (self.status === 'resolved') {
            const x = onFulfilled(self.value);
            resolve(x);
        }

        if (self.status === 'rejected') {
            const x = onRejected(self.reason);
            resolve(x);
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(function () {
                const x = onFulfilled(self.value);
                resolve(x);
            });
            self.onRejectedCallbacks.push(function() {
                const x = onRejected(self.reason);
                resolve(x);
            });
        }
    })
    return promise2;
}

如果失敗拋錯需要執行reject方法,這裡使用try…catch捕獲一下錯誤。也就是判斷then函式的執行結果和promise2的關係。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    const promise2 = new Promise(function (resolve, reject) {
        if (self.status === 'resolved') {
            try {
                const x = onFulfilled(self.value);
                resolve(x);
            } catch(e) {
                reject(e);
            } 
        }

        if (self.status === 'rejected') {
            try {
                const x = onRejected(self.reason);
                resolve(x);
            } catch(e) {
                reject(e);
            } 
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(function () {
                try {
                    const x = onFulfilled(self.value);
                    resolve(x);
                } catch(e) {
                    reject(e);
                } 
            });
            self.onRejectedCallbacks.push(function() {
                try {
                    const x = onRejected(self.reason);
                    resolve(x);
                } catch(e) {
                    reject(e);
                } 
            });
        }
    })
    return promise2;
}

我們知道onFulfilled(self.value)返回的值不一定是一個常量,還可能是個promise,我們這裡寫一個方法來判斷下,如果返回值是promise就呼叫promise,如果不是promise才繼續向resolve傳遞。

我們這裡定義一個resolvePromise方法,來解析promise,在這個函式中我們要判斷返回值x和promse2的關係,所以我們需要傳遞promise2引數,x引數,resolve引數和reject引數。

promise2引數,x引數,resolve引數和reject引數不能直接傳遞至resolvePromise中,因為文件要求他們不能在當前的上下文中執行,所以我們要在整個程式碼塊外層新增setTimeout, 在非同步執行緒中新增。


function resolvePromise (promise2, x, resolve, reject) {

}

Primsie.prototype.then = function(onFulfilled, onRejected) {
    var self = this;
    const promise2 = new Promise(function (resolve, reject) {
        if (self.status === 'resolved') {
            setTimeout(function() {
                try {
                    const x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                } 
            }, 0)
        }

        if (self.status === 'rejected') {
            setTimeout(function() {
                try {
                    const x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            }, 0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function() {
                    try {
                        const x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                }, 0)
            });
            self.onRejectedCallbacks.push(function() {
                setTimeout(function() {
                    try {
                        const x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                }, 0)
            });
        }
    })
    return promise2;
}

resolvePromise這個函式的作用就是判斷x是不是promise,如果是promise就執行他,然後將它的結果呼叫resolve方法,如果x是常量,直接呼叫resolve方法。這些內容在文件上都可以找得到,具體可以自行翻閱文件,這裡就不列出了,直接程式碼實現。

如果promise2和x引用了一個相同的物件,也就是他們是同一個promise物件。

const p = new Promise(function(resolve, reject) {
    resolve('成功');
})
const promise2 = p.then(data => { // 這個時候x和promise2就是相等的,也就是自己等待自己去做做完什麼事,等 和 做某事不能同時執行。
    return promise2;
})

應該丟擲一個型別錯誤作為錯誤原因,resolvePromise中的程式碼實現如下:

function resolvePromise (promise2, x, resolve, reject) {
    if (promise2 === x) { // 防止自己等待自己
        return reject(new TypeError('迴圈引用了'));
    }
}

如果x是物件或者是一個函式,我們就取他的then方法,但是獲取then方法的時候如果出現異常,就執行失敗。因為then方法可能是物件的一個不可訪問的方法,get的時候報異常,所以我們需要使用try…catch去獲取。

如果x不是一個promise,是個普通值,直接呼叫resolve就可以。

function resolvePromise (promise2, x, resolve, reject) {
    if (promise2 === x) { // 防止自己等待自己
        return reject(new TypeError('迴圈引用了'));
    }
    // x是object或者是個function
    if ((x !== null && typeof x === 'object') || typeof x === 'function') {
        try {
            let then = x.then;
        } catch (e) {
            reject(e);
        }
    } else {
        resolve(x);
    }
}

如果then是一個函式,就認為他是Promise, 需要使用call執行then方法,改變this的指向為x, then中傳入成功和失敗的函式, 官方文件中指明成功函式的引數叫y,失敗的引數為r。

如果then不是一個Promise那麼當前這個then是一個普通物件,呼叫resolve方法直接返回即可。

try {
    let then = x.then;
    if (typeof then === 'function') {
        then.call(x, function (y) {
            resolve(y); // 成功的結果,讓promise2變為成功狀態
        }, function (r) {
            reject(r);
        });
    } else {
        resolve(x)
    }
} catch (e) {
    reject(e);
}

這裡面的y有可能也是一個Promise,所以我們這裡不能直接寫resolve(y),應該遞迴判斷y和promise2的關係。所以我們這裡要呼叫resolvePromise。y是then的返回值,和之前的x基本一個概念。

為什麼這裡要用遞迴呢,因為then返回的可能是一個Promise巢狀,也就是Promise中仍舊包含Promise,在Promise的標準這,這樣的寫法是被允許的。所以要用遞迴來解決,拿到最終的返回,也就是基本型別。

try {
    let then = x.then;
    if (typeof then === 'function') {
        then.call(x, function (y) {
            resolvePromise (promise2, y, resolve, reject)
            // resolve(y); // 成功的結果,讓promise2變為成功狀態
        }, function (r) {
            reject(r);
        });
    } else {
        resolve(x)
    }
} catch (e) {
    reject(e);
}

我們的Promise可能會和別人的Promise巢狀使用,官方文件要求,Promise中要書寫判斷,避免對方Promise不規範產生的影響。

比如對方的Promise成功和失敗都呼叫了,或者多次呼叫了成功。需要使用一個called的變數來表示Promise有沒有被呼叫過。

一旦狀態改變就不能再改變了。

function resolvePromise (promise2, x, resolve, reject) {
    if (promise2 === x) { // 防止自己等待自己
        return reject(new TypeError('迴圈引用了'));
    }
    let called; // 表示Promise有沒有被呼叫過
    // x是object或者是個function
    if ((x !== null && typeof x === 'object') || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) { // 是否呼叫過
                        return;
                    }
                    called = true;
                    resolvePromise (promise2, y, resolve, reject)
                }, function (r) {
                    if (called) { // 是否呼叫過
                        return;
                    }
                    called = true;
                    reject(r);
                });
            } else { // 當前then是一個普通物件。
                resolve(x)
            }
        } catch (e) {
            if (called) { // 是否呼叫過
                return;
            }
            called = true;
            reject(e);
        }
    } else {
        if (called) { // 是否呼叫過
            return;
        }
        called = true;
        resolve(x);
    }
}

我們的Promise還存在一個小問題,如果我們的Promise有多個then方法,只在最後一個then方法中傳遞了onFulfilled, 是需要將Promise的返回值傳遞過去的,也就是下面的程式碼需要用內容輸出。這叫值的穿透。

p.then().then().then(function(data) {
    console.log(data);
})

實現起來也比較簡單,假如使用者沒有傳遞onFulfilled,或者傳入的不是函式,我們可以給個預設值,也就是這個引數是一個可選引數。

onRejected的話邏輯也是一樣,只是最後需要將錯誤丟擲,傳遞給下一個reject,如果返回的話會流入下一個resolve。

Primsie.prototype.then = function(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
}

最後我們還存在一個小問題,我們在呼叫executor的時候,可能也會出錯,只要Promise出現錯誤,就需要走到then的reject中,所以我們需要try…catch一下executor。

try {
    executor(resolve, reject);
} catch (e) {
    reject(e);
}

至此,Promise我們就寫完了,全部程式碼如下:

function Promise (executor) {
    var self = this;
    self.status = 'pending';
    self.value;
    self.reason;
    self.onResolvedCallbacks = []; // 存放所有成功的回撥。
    self.onRejectedCallbacks = []; // 存放所有失敗的回撥。
    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) {
    if (promise2 === x) { // 防止自己等待自己
        return reject(new TypeError('迴圈引用了'));
    }
    let called; // 表示Promise有沒有被呼叫過
    // x是object或者是個function
    if ((x !== null && typeof x === 'object') || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) { // 是否呼叫過
                        return;
                    }
                    called = true;
                    resolvePromise (promise2, y, resolve, reject)
                }, function (r) {
                    if (called) { // 是否呼叫過
                        return;
                    }
                    called = true;
                    reject(r);
                });
            } else { // 當前then是一個普通物件。
                resolve(x)
            }
        } catch (e) {
            if (called) { // 是否呼叫過
                return;
            }
            called = true;
            reject(e);
        }
    } else {
        if (called) { // 是否呼叫過
            return;
        }
        called = true;
        resolve(x);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
    var self = this;
    const promise2 = new Promise(function (resolve, reject) {
        if (self.status === 'resolved') {
            setTimeout(function() {
                try {
                    const x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                } 
            }, 0)
        }

        if (self.status === 'rejected') {
            setTimeout(function() {
                try {
                    const x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            }, 0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function() {
                    try {
                        const x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                }, 0)
            });
            self.onRejectedCallbacks.push(function() {
                setTimeout(function() {
                    try {
                        const x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                }, 0)
            });
        }
    })
    return promise2;
}

測試

可以使用promises-aplus-tests測試自己書寫的Promise是否符合規範。

測試的時候需要提供一段指令碼,通過入口進行測試。

Promise.defer = Promise.deferred =  function() {
    let dfd = {};
    dft.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

我們首先安裝promises-aplus-tests包。

npm install promises-aplus-tests -g

安裝成功執行測試指令碼

promises-aplus-tests promise.js

Promise靜態方法實現

Promise.all = function (values) {
    return new Promise(function (resolve, reject) {
        var arr = []; // 最終結果的陣列
        var index = 0;

        function processData (key, value) {
            index++;
            arr[key] = value;
            if (index === values.length) {
                resolve(arr);
            }
        }

        for (var i = 0; i < values.length; i++) {
            var current = values[i];
            if (current && current.then && typeof current.then === 'function') {
                current.then(function(y) {
                    processData(i, y);
                }, reject);
            } else {
                processData(i, current);
            }
        }
    });
}

Promise.race = function (values) {
    return new Promise(function (resolve, reject) {
        for (var i = 0; i < values.length; i++) {
            var current = values[i];
            if (current && current.then && typeof current.then === 'function') {
                current.then(resolve, reject);
            } else {
                resolve(current);
            }
        }
    });
}

Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value);
    });
}

Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason);
    });
}