Promise自定義

自定義代碼

這裏是我自定義的Promise,若是想看原版,能夠跳過最後有符合PromiseA+規範的源碼jquery

class 承諾 {
    constructor(處理器函數) { //1. 處理器函數是在_new 承諾(處理器函數)_的時候當作參數傳入到構造函數裏的,構造函數裏會執行這個處理器函數.
        let self = this //2.註冊函數執行時可能沒有前綴,若是註冊函數裏用this可能就是window,這裏把this賦給變量,造成閉包.
        this.狀態 = '等待...' //3.這個屬性有三個值,默認是等待,只有調用註冊成功或註冊失敗纔會改變這個屬性的值,**只能改變一次**
        this.成功值 = undefined //4.這個成功值是由使用者調用註冊成功函數的時候傳進函數裏的,而後咱們在註冊成功函數給這個屬性賦值,簡單點說就是形參賦值給屬性
        this.失敗緣由 = undefined //5.同上
        //說明:這裏須要瞭解'那時()'函數,能夠看完'那時()'再來看,這兩在異步的狀況纔會用到,使用者在調用'那時()'的時候,承諾仍然是等待狀態(也就是說註冊成功和註冊失敗兩個函數沒有被調用)
        this.成功回調函數組 = [] //6.承諾變爲成功時要調用的函數,屢次調用then傳函數,咱們就把'那時()'傳進來的函數push到這個數組裏,
        this.失敗回調函數組 = [] //7.同上,承諾變爲失敗時要調用的函數

        let 註冊成功 = function (成功值) { //7.'註冊成功()'函數當實參傳進了處理器函數,這樣使用者在編寫處理器函數得時候能夠根據狀況調用.
            if (self.狀態 === '等待...') { //8.這個判斷的做用:承諾的狀態只能由等待->成功或等待->失敗,並且只能改變一次.咱們默認初始態是等待,若是不是等待說明以前已經調用過'註冊成功或註冊失敗'
                self.成功值 = 成功值 //9.將使用者調用'註冊成功(成功值)'函數傳進來的值由對象屬性保存,這個值在調用'那時()'傳進來的函數時會用到
                self.狀態 = '成功' //10.調用註冊成功,改變承諾狀態爲成功
                for (let 函數 of self.成功回調函數組) { //11.調用'那時()'傳進來的函數
                    函數(成功值)
                }
                // this.成功回調函數組.forEach(函數=>函數())
            }

        }
        let 註冊失敗 = function (失敗緣由) { //12. 同上
            if (self.狀態 === '等待...') {
                self.失敗緣由 = 失敗緣由
                self.狀態 = '失敗'
                for (let 函數 of self.失敗回調函數組) {
                    函數(失敗緣由)
                }
                // this.成功回調函數組.forEach(函數=>函數())
            }

        }


        try {
            處理器函數(註冊成功, 註冊失敗) //13.執行處理器函數,把咱們定義好的兩個函數傳進去,這樣使用者就能夠用這兩個函數來改變承諾的狀態和值
        } catch (錯誤) {
            註冊失敗(錯誤) //14.處理器函數是使用者編寫的,有可能報錯,出錯了咱們就調用註冊失敗()來改變這個承諾的狀態和值
        }
    }

    那時(成功的回調, 失敗的回調) { //15.對象的'那時()'方法,由使用者傳進來兩個函數參數,規定當前承諾對象爲成功時調用第一個,失敗調用第二個
        let self = this
        成功的回調 = typeof 成功的回調 === 'function' ? 成功的回調 : 成功值 => 成功值
        失敗的回調 = typeof 失敗的回調 === 'function' ? 失敗的回調 : 失敗緣由 => {
            throw 失敗緣由
        } //16.這兩個參數是可選參數,當他們不是函數時咱們給他默認值

        let 新的承諾 = new 承諾((註冊成功, 註冊失敗) => { //17.返回一個新的承諾,這樣就能夠鏈式調用'那時()'了,這裏要注意新承諾的狀態和值由'那時()'傳進來的函數執行狀況決定
            //18.判斷承諾的狀態,決定當即執行回調仍是將回調函數push到回調函數組裏等使用者調用註冊成功()在註冊成功()裏執行
            if (self.狀態 === '等待...') { //19.若是是等待,顯然承諾的成功值或失敗值仍是undefined,因此咱們把他push到回調函數組裏,讓他在註冊成功()或註冊失敗()函數裏調用
                self.成功回調函數組.push(() => { //20.這個函數要異步,由於咱們會在裏面用到新承諾,然而新承諾如今還沒被賦值,要完全理解這裏應該須要js執行機制知識,目前我尚未....
                    setTimeout(() => {
                        try {
                            let 回調的返回值 = 成功的回調(self.成功值) //21.調用使用者傳進來的函數獲得返回值,若是使用者沒有寫返回語句默認是返回undefined
                            善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) //22.根據返回值決定咱們這個新承諾的值和狀態 這裏傳進去的是咱們新承諾的註冊成功和註冊失敗函數,咱們在裏面調用他們來改變咱們新承諾的狀態
                        } catch (錯誤) {
                            註冊失敗(錯誤) //23.若是執行使用者的函數出錯就把咱們新承諾的狀態和值改變
                        }

                    })
                })

                self.失敗回調函數組.push(() => { //24.同上
                    setTimeout(() => {
                        try {
                            let 回調的返回值 = 失敗的回調(self.失敗緣由)
                            善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
                        } catch (錯誤) {
                            註冊失敗(錯誤)
                        }
                    })
                })

            }
            if (self.狀態 === '成功') { //25.若是承諾的狀態是成功的說明註冊成功()函數已經被調用了,承諾的狀態和值都被使用者改變了,
                //咱們能夠取到對應的值來傳進回調裏,讓使用者用.
                setTimeout(() => {
                    try { //邏輯同20-23
                        let 回調的返回值 = 成功的回調(self.成功值)
                        善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
                    } catch (錯誤) {
                        註冊失敗(錯誤)
                    }

                })
            }
            if (self.狀態 === '失敗') { //同上
                setTimeout(() => {
                    try {
                        let 回調的返回值 = 失敗的回調(self.失敗緣由)
                        善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
                    } catch (錯誤) {
                        註冊失敗(錯誤)
                    }
                })
            }
        })


        return 新的承諾

    }

}

工具函數,這個函數纔是真核心

//26.這個是核心,涉及到遞歸,這裏我寫的和PromiseA+規範的實現不一樣,他判斷的是thenable,兼容性好,我只是爲了理解Promise原理因此就簡化了.

function 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) {

    // let p2 = p.那時((成功值)=>{
    //     return p2
    // })

    if (回調的返回值 instanceof 承諾) { //27.判斷使用者寫的回調函數的返回值是否是一個承諾,若是不是承諾就直接調用咱們新承諾的註冊成功()函數改變咱們新承諾的狀態和值
        //若是返回值是一個承諾咱們就獲得這個承諾的值,把這個值給咱們新承諾的註冊函數
        if (新的承諾 === 回調的返回值) {
            //28.這裏是爲了解決這種使用狀況
            // let p2 = p.那時((成功值)=>{
            //     return p2
            // })
            註冊失敗(new TypeError('循環引用'))
            return
        }
        try {
            回調的返回值.那時((成功值) => {
                善後處理(新的承諾, 成功值, 註冊成功, 註冊失敗) //29.若是使用者返回的承諾的值仍是一個承諾,繼續'那時()'直到不是承諾
                //注意:這裏傳進去的註冊成功,註冊失敗是咱們新承諾的註冊函數,遞歸進去,當不是承諾時就改變咱們新承諾的狀態和值了,而後遞歸一層層返回
                //這裏是進遞歸,其餘狀況就是出遞歸
            }, (失敗緣由) => {
                註冊失敗(失敗緣由)
            })
        } catch (錯誤) {
            註冊失敗(錯誤)
        }
    } else {
        註冊成功(回調的返回值)
    }


}

自定義完成,咱們拉出來遛一遛

//測試一

p = new 承諾((註冊成功, 註冊失敗) => {
    setTimeout(()=>{
        註冊失敗('abc')
    },1000)

})

p.那時((成功的值) => {
    console.log(成功的值)

},(失敗緣由)=>{
    console.log(失敗緣由)
})
//輸出abc
//測試二:返回值是承諾

p = new 承諾((註冊成功, 註冊失敗) => {
    setTimeout(() => {
        註冊成功('我是最外面的')
    }, 1000)

})

p.那時((成功值) => {
        console.log(成功值)
        let p11 = new 承諾((註冊成功, 註冊失敗) => {
            let p22 = new 承諾((註冊成功, 註冊失敗) => {
                註冊成功('最裏層')
            })
            註冊成功(p22)
        })
        return p11
    }, 1)
    .那時((成功值) => {
        console.log(成功值)
    }, (失敗緣由) => {
        console.log(失敗緣由)
    })
//輸出:
//我是最外層
//我是最裏層
//測試三:失敗穿透
p = new 承諾((註冊成功, 註冊失敗) => {
    throw '(╥╯^╰╥)'
    setTimeout(() => {
        註冊成功('♪(´▽`)')

    }, 1000)

})

p.那時((成功的值) => {
    console.log(成功的值)
    
},(失敗緣由)=>{
    console.log('第一次失敗:'+失敗緣由)
    throw '(╥╯^╰╥))'
}).那時(
    (成功的值) => {
        console.log(成功的值)

    }
).那時(null,(失敗緣由) => {
    console.log('失敗穿透'+失敗緣由)
})
//輸出
//第一次失敗:(╥╯^╰╥)
//失敗穿透(╥╯^╰╥))

到這裏就結束了,下面是經過PromiseA+測試的源代碼.

function Promise(executor) {
    let self = this;
    self.value = undefined; // 成功的值
    self.reason = undefined; // 失敗的值
    self.status = 'pending'; // 目前promise的狀態pending
    self.onResolvedCallbacks = []; // 可能new Promise的時候會存在異步操做,把成功和失敗的回調保存起來
    self.onRejectedCallbacks = [];

    function resolve(value) { // 把狀態更改成成功
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲成功態
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的成功回調保存起來
        }
    }

    function reject(reason) { // 把狀態更改成失敗
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲失敗態
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的失敗回調保存起來
        }
    }
    try {
        // 在new Promise的時候,當即執行的函數,稱爲執行器
        executor(resolve, reject);
    } catch (e) { // 若是執行executor拋出錯誤,則會走失敗reject
        reject(e);
    }
}
// then調用的時候,都是屬於異步,是一個微任務
// 微任務會比宏任務先執行
// onFulfilled爲成功的回調,onRejected爲失敗的回調
Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
        throw err
    }
    let self = this;
    let promise2;
    // 上面講了,promise和jquery的區別,promise不能單純返回自身,
    // 而是每次都是返回一個新的promise,才能夠實現鏈式調用,
    // 由於同一個promise的pending resolve reject只能更改一次
    promise2 = new Promise((resolve, reject) => {
        if (this.status === 'resolved') {
            // 爲何要加setTimeout?
            // 首先是promiseA+規範要求的
            // 其次是你們寫的代碼,有的是同步,有的是異步
            // 因此爲了更加統一,就使用爲setTimeout變爲異步了,保持一致性
            setTimeout(() => {
                try { // 上面executor雖然使用try catch捕捉錯誤
                    // 可是在異步中,不必定可以捕捉,因此在這裏
                    // 用try catch捕捉
                    let x = onFulfilled(self.value);
                    // 在then中,返回值多是一個promise,因此
                    // 須要resolvePromise對返回值進行判斷
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0)
        }
        if (self.status === 'rejected') {
            setTimeout(() => {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            });
        }
    });
    return promise2
}


function resolvePromise(promise2, x, resolve, reject) {
    // 3.從2中咱們能夠得出,本身不能等於本身
    // 當promise2和x是同一個對象的時候,則走reject
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'))
    }
    // 4.由於then中的返回值能夠爲promise,當x爲對象或者函數,纔有可能返回的是promise
    let called
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 8.從第7步,能夠看出爲何會存在拋出異常的可能,因此使用try catch處理
        try {
            // 6.由於當x爲promise的話,是存在then方法的
            // 可是咱們取一個對象上的屬性,也有可能出現異常,咱們能夠看一下第7步
            let then = x.then

            // 9.咱們爲何在這裏用call呢?解決了什麼問題呢?能夠看上面的第10步
            // x可能仍是個promise,那麼就讓這個promise執行
            // 可是仍是存在一個惡做劇的狀況,就是{then:{}}
            // 此時須要新增一個判斷then是否函數
            if (typeof then === 'function') {
                then.call(x, (y) => { // y是返回promise後的成功結果
                    // 一開始咱們在這裏寫的是resolve(y),可是考慮到一點
                    // 這個y,有可能仍是一個promise,
                    // 也就是說resolve(new Promise(...))
                    // 因此涉及到遞歸,咱們把resolve(y)改爲如下

                    // 12.限制既調resolve,也調reject
                    if (called) return
                    called = true

                    resolvePromise(promise2, y, resolve, reject)
                    // 這樣的話,代碼會一直遞歸,取到最後一層promise

                    // 11.這裏有一種狀況,就是不能既調成功也調失敗,只能挑一次,
                    // 可是咱們前面不是處理過這個狀況了嗎?
                    // 理論上是這樣的,可是咱們前面也說了,resolvePromise這個函數
                    // 是全部promise通用的,也能夠是別人寫的promise,若是別人
                    // 的promise可能既會調resolve也會調reject,那麼就會出問題了,因此咱們接下來要
                    // 作一下限制,這個咱們寫在第12步

                }, (err) => { // err是返回promise後的失敗結果
                    if (called) return
                    called = true
                    reject(err)
                })
            } else {
                if (called) return;
                called = true;
                resolve(x) // 若是then不是函數的話,那麼則是普通對象,直接走resolve成功
            }
        } catch (e) { // 當出現異常則直接走reject失敗
            if (called) return
            called = true
            reject(e)
        }
    } else { // 5.x爲一個常量,則是走resolve成功
        resolve(x)
    }
}







module.exports = Promise;

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
//執行命令promises-aplus-tests promise.js檢測是否符合promiseA+規範,先安裝包
相關文章
相關標籤/搜索