JS 非同步發展流程 —— Promise

Desmonddai583發表於2018-04-01

實現一個自己的Promise庫

上一篇中我們介紹了整個js非同步發展的過程,今天我們就來自己完成一個Promise庫,可以參照Promise的實現規範

首先先搭一個最簡單的Promise框架

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    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;
        }
    }
    executor(resolve,reject)
}
Promise.prototype.then = function(onFulfilled,onRjected){
    let self = this;
    if(self.status === 'resolved'){
        onFulfilled(self.value);
    }
    if(self.status === 'rejected'){
        onRjected(self.reason);
    }
}

module.exports = Promise
複製程式碼

這裡可以看到我們給Promise物件定義了3種狀態以及一個用來當做預設成功會傳入的值value和失敗會傳入的原因reason,同時定義了resolve和reject方法,將它們傳入executor,也就是我們在呼叫生成Promise物件時會傳入的執行函式,裡面放著我們需要呼叫的非同步邏輯.

接著我們通過Promise的原型定義了一個then的公有方法,它會接受兩個引數onFullfilled和onRejected,這兩個引數其實就是兩個函式,是我們用來定義成功或失敗時所執行的邏輯.

但是這裡的問題是當我們executor裡執行的是非同步邏輯時,如果我們呼叫then,其實status依舊是pending的狀態也就根本不會呼叫到onFullfilled或onRejected,所以我們需要定義兩個callback陣列來存放我們在pending狀態下呼叫過的onFullfilled和onRejected函式,同時,在resolve和reject函式中我們需要將對應的callback陣列中的onFullfilled和onRejected依次取出呼叫.

所以讓我們來看下改進之後的程式碼:

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    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();
            })
        }
    }
    executor(resolve, reject)
}

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    if (self.status === 'resolved') {
            onFulfilled(self.value);
    }
    if (self.status === 'rejected') {
            onRjected(self.reason);
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                onFulfilled(self.value);
            });
            self.onRejectedCallbacks.push(function () {
                onRjected(self.reason);
            });
    }
}
複製程式碼

下一步,我們就要開始考慮如何支援鏈式呼叫的情況了,如前文所說,鏈式呼叫要求我們每次呼叫then之後都需要返回一個新的Promise物件.

所以在我們需要對onFullfilled和onRejected返回的結果進行判斷分析來決定之後then呼叫時是走向成功還是失敗。返回的值有多種情況,它可能是一個值,也可能是另一個Promise物件。所以讓我們定義一個新的函式用來解析.

function resolvePromise(p2,x,resolve,reject){
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if(p2===x){ //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
     if(x!==null||(typeof x === 'object'||typeof x === 'function')){
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try{ // {then:1}
            let then = x.then;
            if(typeof then === 'function'){
                // 成功
                then.call(x,function(y){
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2,y,resolve,reject)
                },function(err){ //失敗
                    reject(err);
                })
            }else{
                resolve(x)
            }
        }catch(e){
            reject(e);
        }
     }else{ // 說明是一個普通值1
        resolve(x); // 表示成功了
     }
}
複製程式碼

這裡判斷的邏輯是先判斷返回值x是不是指向當前將會返回的Promise物件自己,如果是自己的話就變成是自己等待自己,那我們就直接丟擲一個迴圈引用的錯誤,然後接著判斷x是不是一個物件,如果不是就說明是普通值,我們直接返回成功resolve並傳入值x,如果是物件的話並且有一個then的方法的話我們就當他是一個Promise物件,如果不是Promise物件的話我們也就直接返回成功resolve並傳入物件x,如果是的話那我們就直接呼叫then方法去判定它的成功與失敗,失敗的話我們就直接呼叫reject,成功的話我們還需在判斷成功的值是否為Promise物件,如果不是的話我們可以直接resolve,是的話則我們要繼續遞迴呼叫resolvePromise方法去解析一直到獲取一個值位置.

我們看一下目前為止的程式碼:

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    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(p2,x,resolve,reject){
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if(p2===x){ //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
     if(x!==null||(typeof x === 'object'||typeof x === 'function')){
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try{ // {then:1}
            let then = x.then;
            if(typeof then === 'function'){
                // 成功
                then.call(x,function(y){
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2,y,resolve,reject)
                },function(err){ //失敗
                    reject(err);
                })
            }else{
                resolve(x)
            }
        }catch(e){
            reject(e);
        }
     }else{ // 說明是一個普通值1
        resolve(x); // 表示成功了
     }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
            // x可能是一個promise 也有可能是一個普通的值
            let x = onFulfilled(self.value);
            // x可能是別人promise,寫一個方法統一處理
            resolvePromise(promise2,x,resolve,reject);
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
             let x = onRjected(self.reason);
             resolvePromise(promise2,x,resolve,reject);
        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                let x = onFulfilled(self.value);
                resolvePromise(promise2,x,resolve,reject);
            });
            self.onRejectedCallbacks.push(function () {
                let x = onRjected(self.reason);
                resolvePromise(promise2,x,resolve,reject);
            });
        })
    }
    return promise2;
}

module.exports = Promise
複製程式碼

下一步,由於規範中要求返回的Promise物件中的onFullfilled和onRejected執行邏輯必須遵從是非同步的,所以我們可以用一個setTimeout函式來包住執行邏輯

setTimeout(function(){
    try{
        let x = onFulfilled(self.value);
        // x可能是別人promise,寫一個方法統一處理
        resolvePromise(promise2, x, resolve, reject);
    }catch(e){
        reject(e);
    }
})
複製程式碼

同時如果呼叫then時沒有傳入onFullfilled或onRejected的話我們可以定義一個預設的函式給它們,這樣就可以解決到值穿透的問題,例如promise.then().then().then()這樣的呼叫

onFulfilled = typeof onFulfilled === 'function'?onFulfilled:function(value){
    return value;
}
onRjected = typeof onRjected === 'function'?onRjected:function(err){
    throw err;
}
複製程式碼

另外一個問題就是有些Promise的實現可能可以既呼叫成功又失敗,那麼對於這種情況我們就需要專門處理,永遠只處理第一個呼叫的

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try { // {then:1}
            let then = x.then;
            if (typeof then === 'function') {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
複製程式碼

以上我們就完成了一個滿足規範的Promise實現了,最後的實現程式碼如下:

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    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) {
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try { // {then:1}
            let then = x.then;
            if (typeof then === 'function') {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失敗預設不穿給一個函式
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
            // x可能是一個promise 也有可能是一個普通的值
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是別人promise,寫一個方法統一處理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })

        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

module.exports = Promise;
複製程式碼

對於以上程式碼實現,我們可以通過一個promises-aplus-tests的庫來驗證是否達成規範要求

可以通過npm install -g promises-aplus-tests,使用時直接promises-aplus-tests 檔名即可

接下來讓我們來實現一個類似於之前介紹的Q庫中defer的語法糖,首先我們看一下以下這段程式碼:

function read() {
    return new Promise(function(resolve,reject) {
        require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
            if(err) reject(err);
            resolve(data);
        })
    })
}
read().then(function (data) {
    console.log(data)
},function(err){
    console.log(err);
})
複製程式碼

這裡我們封裝了一個read函式,並返回一個Promise物件,但是可以看到這裡我們必須在非同步邏輯外面包一層Promise.我們可以通過定義Promise.defer的這樣一個語法糖來簡化這一層程式碼.

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

let Promise = require('./Promise');
function read() {
    let defer = Promise.defer()
    require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
read().then(function (data) {
    console.log(data)
},function(err){
    console.log(err);
})
複製程式碼

同理包括catch,all,race,resolve,reject我們都可以自己實現,這裡就不多加贅述,直接上最終的程式碼:

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    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) {
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
        return reject(new TypeError('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        try { // {then:1}
            let then = x.then;
            if (typeof then === 'function') {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失敗預設不穿給一個函式
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
            // x可能是一個promise 也有可能是一個普通的值
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是別人promise,寫一個方法統一處理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })

        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

// 捕獲錯誤的方法
Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

// 解析全部方法
// let arr = [];
// arr[1] = 100;
// console.log(arr.length)
Promise.all = function (promises) {
    //promises是一個promise的陣列
    return new Promise(function (resolve, reject) {
        let arr = []; //arr是最終返回值的結果
        let i = 0; // 表示成功了多少次
        function processData(index, y) {
            arr[index] = y;
            if (++i === promises.length) {
                resolve(arr);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function (y) {
                processData(i, y)
            }, reject)
        }
    })
}

// 只要有一個promise成功了 就算成功。如果第一個失敗了就失敗了
Promise.race = function (promises) {
    return new Promise(function (resolve, reject) {
        for (var i = 0; i < promises.length; i++) {
            promises[i].then(resolve,reject)
        }
    })
}

// 生成一個成功的promise
Promise.resolve = function(value){
    return new Promise(function(resolve,reject){
        resolve(value);
    })
}

// 生成一個失敗的promise
Promise.reject = function(reason){
    return new Promise(function(resolve,reject){
        reject(reason);
    })
}

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

module.exports = Promise;
複製程式碼

相關文章