手把手教你根據PromiseA+手寫一個Promise類

好想吃涼皮發表於2018-04-06

什麼是Promise

Promise 是一個建構函式, new Promise 返回一個 promise物件 接收一個excutor執行函式作為引數, excutor有兩個函式型別形參resolve reject

其用法是
let p = new Promise(function (resolve,reject){
    resolve("這是promise")
    reject("這個不執行")

})
p.then(function (data) {
    console.log(data)
},function (err) {
    console.log(err)
})複製程式碼

開始寫一個Promise

1,我們從用法中就可以看出new Promise裡面是一個函式,而函式裡的傳參是兩個函式-resolve和reject。要明白,promise執行的時候有三種狀態:

  1. pending
  2. resolve(成功)
  3. reject(失敗)

只要成功就不會失敗,只要失敗就不會成功,不會又成功又失敗。走了resolve就不會走reject,寫了也沒用。promise規範

function Promise(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) {
        _self.status = "rejected"
        _self.reason = reason
    }
    try{  //捕獲異常
        executor(resolve,reject); //promise 引數是一個函式,函式中的倆個引數是兩個函式
    }catch (e){
       reject(e)
    }
}
//promise例項上有個then方法。
Promise.prototype.then = function (onFulfilled,onRejected) {
    let _sele = this;
    if(_sele.status === "resolved"){
        onFulfilled(_sele.value)
    }
    if(_sele.status === "rejected"){
        onRejected(_sele.reason)
    }

}

module.exports = Promise;複製程式碼
一個簡單的promise模組就寫好了引入執行一下
let Promise = require('./promise')
let p = new Promise(function (resolve,reject){
    resolve("這是promise")

})
p.then(function (data) {
    console.log(data)
},function (err) {
    console.log(err)
})複製程式碼

2,如果resolve 是需要非同步執行,如下

let Promise = require('./promise')
let p = new Promise(function (resolve,reject){
    setTimeout(function () {  //給一個延遲
        resolve("這是promise")
    })


})
p.then(function (data) {
    console.log(data)
},function (err) {
    console.log(err)
})複製程式碼
這樣的話執行結果是空,沒有執行出來,因為我們類裡寫的then方法是同步執行,當執行到判斷status的值的時候,因為setTimeout的原因,狀態值還沒改變過來,所以程式碼會一直停在那,所以我們在這種情況下就要給一個空間來存放這些沒有狀態的操作,當狀態明確了再執行。那麼聽起來就需要給一個陣列結構空間了。
function Promise(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;
            //判斷好了狀態,要迴圈執行出來,這裡為什麼會是陣列,是因為會有:p.then();p.then(),多次執行的狀態,不過只要有一個成功就是成功
            _self.onResolvedCallbacks.forEach(function (fn) {
                fn()
            })
        }
    };
    function reject(reason) { // 失敗狀態
        _self.status = "rejected";
        _self.reason = reason;
         _self.onRejectedCallbacks.forEach(function (fn) {
             fn()
         })
    }
    try{
        executor(resolve,reject);
    }catch (e){ // 捕獲的時候發生異常,就直接失敗了
       reject(e)
    }
}
//promise例項上有個then方法。
Promise.prototype.then = function (onFulfilled,onRejected) {
    let _sele = this;
    if(_sele.status === "resolved"){
        onFulfilled(_sele.value)
    }
    if(_sele.status === "rejected"){
        onRejected(_sele.reason)
    }
    if(_sele.status === "pending"){
        //把這個狀態的都儲存起來
        _sele.onResolvedCallbacks.push(function () {
            onFulfilled(_sele.value)
        })
        _sele.onRejectedCallbacks.push(function () {
            onRejected(_sele.reason)
        })
    }

}

module.exports = Promise;複製程式碼
3,鏈式呼叫,promise可以實現鏈式呼叫。方便又實惠。執行程式碼如下:
let promise = new Promise(function (resolve,reject) {
    resolve(100)

})
//鏈式呼叫的特點,將第一次成功的返回值當成下一次回撥函式的引數
promise.then(function (data) {
  throw new Error("123")
},function () {

}).then(function (data) {
    console.log(data);
},function(err){
    console.log(err)
})

//鏈式呼叫:靠的是返回一個新的promise複製程式碼
但其實也可以這樣寫:
let p2 = promise.then(function (data) {
    return data  //這裡返回的只要不是錯誤,下次走的還是成功,
},function () {

})
p2.then(function (data) {
    console.log(data);
},function(err){
    console.log(err)
})複製程式碼

這裡不等價於(返回資料的化直接讓下一次的promise 成功 如果返回的是promise就等待這個promise執行結果)

let p2 = promise.then(function (data) {
    return new Promise(function (resolve,reject) {
        resolve(data)
    })
},function () {

})
p2.then(function (data) {
    console.log(data);
},function(err){
    console.log(err)
})複製程式碼
正確的執行結果應該是100,那麼開始寫吧,(重頭戲)
function Promise(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;
            //判斷好了狀態,要迴圈執行出來,這裡為什麼會是陣列,是因為會有:p.then();p.then(),多次執行的狀態,不過只要有一個成功就是成功
            _self.onResolvedCallbacks.forEach(function (fn) {
                fn()
            })
        }
    };
    function reject(reason) { // 失敗狀態
        _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){  //首先要判斷返回的x不能跟原來的promise相等,如果是的話就是自己等待自己了!。
       return reject(new TypeError("不能迴圈引用")); //這裡應該報一個型別錯誤,有問題
    }
    let called; //表示是否調過
    if(x !== null &&(typeof x ==='object'||typeof x ==='function')){ //判斷返回的x是不是一個promise,promise應該是一個物件;這裡要把null排除因為typeof null是object
        try{
            //  1,看看返回的這個物件或方法裡有沒有then方法
            let then = x.then;//這裡then可能是{then:1}
            if(typeof then  === 'function'){  //說明是一個promise
                then.call(x,function (y) {  //call是為了this指向x,
                    //2,再看是否被呼叫過
                    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{
        resolve(x)  //如果是常量就直接resolve;
    }
}


//promise例項上有個then方法。
Promise.prototype.then = function (onFulfilled,onRejected) {
    let _sele = this;
    //首先要先定義一個新的promise
    let promise2;
    if(_sele.status === "resolved"){
        promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () {  //為了測試時 效果統一 因為很多人的實現 onfufiled 和 onrejected是非同步執行,為了保證測試統一 所以全部非同步
                try {   //捕獲異常
                    let x =  onFulfilled(_sele.value)  //宣告一個x來接受返回值
                    resolvePromise(promise2,x,resolve,reject)
                }catch (e){
                    reject(e)
                }
            })

        })

    }
    if(_sele.status === "rejected"){
        promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () {  //為了測試時 效果統一 因為很多人的實現 onfufiled 和 onrejected是非同步執行,為了保證測試統一 所以全部非同步
                try {   //捕獲異常
                    let x =   onRejected(_sele.reason)  //宣告一個x來接受返回值
                    resolvePromise(promise2,x,resolve,reject)
                }catch (e){
                    reject(e)
                }
            })

        })

    }
    // 當呼叫then時可能沒成功 也沒失敗
    if(_sele.status === "pending"){
        promise2 = new Promise(function (resolve,reject) {
            //把這個狀態的都儲存起來
            _sele.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(_sele.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            _sele.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRejected(_sele.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })

    }
    return promise2;
}

module.exports = Promise;複製程式碼

對於promise2 === x 什麼情況下出現

var p2 = promise.then(function (data) {
    return p2
},function () {

})
p2.then(function (data) {
    console.log(data);
},function(err){
    console.log(err)
})複製程式碼

4,如果有人這樣執行程式碼也要考慮進去

let p2 = promise.then(function (data) {
    return new Promise(function (resolve,reject) {
        resolve(data)
    })
},function () {

})
p2.then().then().then(function (data) {//多個then()空呼叫
    console.log(data);
},function(err){
    console.log(err)
})複製程式碼
那我們就要對類裡then方法裡的回撥做判斷了
        if(_self.status === "pending"){function Promise(executor) {
    let  _self = this;
    _self.status = "pending";
    _self.value = undefined;//成功的原因;
    _self.reason = undefined;//失敗的原因;
    _self.onResolvedCallbacks = []; // 存放then成功的回撥
    _self.onRejectedCallbacks = []; // 存放then失敗的回撥

    function resolve(value) { // 成功狀態
// value可能是別人的promise
        if(value !==null&&(typeof value === 'object'||typeof value === 'function')){  //這種情況是為了判斷 resolve(New promise)
                return value.then(resolve,reject)

            }
        if(_self.status === "pending"){
            _self.status = "resolved";
            _self.value = value;
            //判斷好了狀態,要迴圈執行出來,這裡為什麼會是陣列,是因為會有:p.then();p.then(),多次執行的狀態,不過只要有一個成功就是成功
            _self.onResolvedCallbacks.forEach(function (fn) {
                fn()
            })
        }
    };
    function reject(reason) { // 失敗狀態
        _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){  //首先要判斷返回的x不能跟原來的promise相等。
       return reject(new TypeError("不能迴圈引用")); //這裡應該報一個型別錯誤,有問題
    }
    let called; //表示是否調過
    if(x !== null &&(typeof x ==='object'||typeof x ==='function')){ //判斷返回的x是不是一個promise,promise應該是一個物件;這裡要把null排除因為typeof null是object
        try{
            //  1,看看返回的這個物件或方法裡有沒有then方法
            let then = x.then;//這裡then可能是{then:1}
            if(typeof then  === 'function'){  //說明是一個promise
                then.call(x,function (y) {  //call是為了this指向x,
                    //2,再看是否被呼叫過
                    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{
        resolve(x)  //如果是常量就直接resolve;
    }
}


//promise例項上有個then方法。
Promise.prototype.then = function (onFulfilled,onRejected) {
    //成功和失敗預設不穿給一個函式
    onFulfilled = typeof onFulfilled ==='function'?onFulfilled:function (value) {
        return value
    };
    onRejected = typeof  onRejected ==='function' ? onRejected :function (err) {
        throw err
    }
    let _sele = this;
    //首先要先定義一個新的promise
    let promise2;
    if(_sele.status === "resolved"){
        promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () {  //為了測試時 效果統一 因為很多人的實現 onfufiled 和 onrejected是非同步執行,為了保證測試統一 所以全部非同步
                try {   //捕獲異常
                    let x =  onFulfilled(_sele.value)  //宣告一個x來接受返回值
                    resolvePromise(promise2,x,resolve,reject)
                }catch (e){
                    reject(e)
                }
            })

        })

    }
    if(_sele.status === "rejected"){
        promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () {  //為了測試時 效果統一 因為很多人的實現 onfufiled 和 onrejected是非同步執行,為了保證測試統一 所以全部非同步
                try {   //捕獲異常
                    let x =   onRejected(_sele.reason)  //宣告一個x來接受返回值
                    resolvePromise(promise2,x,resolve,reject)
                }catch (e){
                    reject(e)
                }
            })

        })

    }
    // 當呼叫then時可能沒成功 也沒失敗
    if(_sele.status === "pending"){
        promise2 = new Promise(function (resolve,reject) {
            //把這個狀態的都儲存起來
            _sele.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(_sele.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            _sele.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRejected(_sele.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })

    }
    return promise2;
}

module.exports = Promise;複製程式碼
好了,一個完整的promise寫完了,同學們聯絡的時候一定要記得引入自己寫的promise檔案哦!

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

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

在Promise類上的掛方法

實現promise類上的 call,catch,race,resolve,reject等方法

// 捕獲錯誤的方法
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[i].then(function(data){
        //            resolve(data)  //只接收一個resolve,因為他們同屬於最外層的new promise。
        //        },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
}複製程式碼
用法如下
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);
})
複製程式碼











相關文章