Promise徹底解讀

Promise簡介

是一個保存了異步事件將來執行結果的對象。它不是一個新技術,而是一種能夠優化異步編程編碼風格的規範。最先誕生於社區,用於解決JavaScript代碼中回調嵌套層級過多的問題和錯誤處理邏輯堆砌的問題。使用Promise對象封裝異步操做,可讓代碼變得直觀、線性、優雅。javascript

Promise基本使用

用Promise封裝Ajax請求java

//先寫一個原始的Ajax請求
let xhr = new XMLHttpRequest()
function resolve(v){console.log(v);}
function reject(e){console.log(e);}
xhr.onerror = function(e){
    reject(e)
}
xhr.ontimeout = function(e){
    reject(e)
}
xhr.onreadystatechange = function(){
    if(xhr.readyState===4){
        if(xhr.status===200){
            resolve(xhr.response)
        }
    }
}
xhr.open('Get','https://wwww.google.com',true)
xhr.send()

// 初版:利用Promise封裝ajax
let p = new Promise((resolve,reject)=>{
    let xhr = new XMLHttpRequest()
    xhr.onerror = function(e){
        reject(e)
    }
    xhr.ontimeout = function(e){
        reject(e)
    }
    xhr.onreadystatechange = function(){
        if(xhr.readyState===4){
            if(xhr.status===200){
                resolve(xhr.response)
            }
        }
    }
    xhr.open('Get','https://wwww.google.com',true)
    xhr.send()
});

p.then(v=>{
    console.log("我是成功時註冊的回調");
},e=>{
    console.log("我是失敗時註冊的回調");
})

// 第二版 支持傳參、封裝了Promise建立的細節
function Xpromise(request){
    function executor(request,resolve,reject){
        let xhr = new XMLHttpRequest()
        xhr.onerror = function(e){
            reject(e)
        }
        xhr.ontimeout = function(e){
            reject(e)
        }
        xhr.onreadystatechange = function(){
            if(xhr.readyState===4){
                if(xhr.status===200){
                    resolve(xhr.response)
                }
            }
        }
        xhr.open('Get',request.url,true)
        xhr.send()
    }
    renturn new Promise(executor);
}

let x1 = Xpromise(makeRequest('https://wwww.google.com')).then(v=>{
    console.log("我是成功時註冊的回調");
},e=>{
    console.log("我是失敗時註冊的回調");
})

另外,Promise還提供了一系列好用的API,如靜態resolve()、all()、race()方法等。node

實現原理概述

Promise用回調函數延遲綁定回調函數onResolve返回值穿透機制解決回調嵌套層級過多的問題;使用錯誤冒泡機制簡化了錯誤處理邏輯堆砌的問題。ajax

手工實現一個Promise

初版

// 第一點:Promise是一個類
class MyPromise {
    // 第二點:Promised構造函數的參數是一個函數;
    constructor(fn) {
        if (typeof fn !== "function") {
            throw new Error("promise的構造函數參數應該爲函數類型")
        }
        // 第三點:Promise的內部狀態有三個,Promise對象具備值
        this._status = PENDING;
        this._value = undefined;
        // 第五點:new Promise(fn)時,就須要執行fn作業務邏輯,故構造函數裏就要調用fn函數。此處內部函數_resolve和_reject會被調用用於追蹤Promise的內部狀態
        try {
            //注意用try-catch包住
            fn(this._resolve.bind(this), this._reject.bind(this));
        } catch (err) {
            this._reject(err)
        }
    }
    // 第四點:定義MyPromise狀態翻轉時,要執行的內部函數
    _resolve(val){
        if (this._status !== this.PENDING) return //這段代碼體現了Promise的狀態翻轉:只能是P->F或者是P->R
        this._status = FULLFILLED;
        this._value = val;
    };
    _reject(err){
        if (this._status !== this.PENDING) return
        this._status = REJECTED;
        this._value = err;
    };
}

第二版

class MyPromise {
    constructor(fn) {
        if (typeof fn !== "function") {
            throw new Error("myPromise的構造函數參數應該爲函數類型")
        }
        this._status = PENDING;
        this._value = undefined;
        //特性2-新增定義兩個回調函數數組
        this.fulfilledQueue = [];
        this.rejectedQueue = [];
        try {
            fn(this._resolve.bind(this), this._reject.bind(this));
        } catch (err) {
            this._reject(err)
        }
    }
    _resolve(val){
        if (this._status !== this.PENDING) return
        // 特性4:註冊的回調函數在Promise狀態翻轉時會執行,執行的方式是循環從隊列裏面取出回調執行
        // 定義run函數
        run = (value)=>{
            this._status = FULLFILLED;
            this._value = val;
            let ck;
            while(ck = this.fulfilledQueue.shift()){
                ck(value);
            }
        }
        // 特性5:run()函數的執行,這裏很是關鍵。要把run放進setTimeOut。爲何?
        // 由於執行_resolve()函數時,then()可能還沒執行,因此爲了讓then()中的回調、包括鏈式調用的then()的回調添加到fulfilledQueue中,
        // 須要延遲執行run()。實際上這裏用setTimeout性能差,實際中採用微任務的方式實現
        setTimeout(run,0)
        //run();
    };
    _reject(err){
        if (this._status !== this.PENDING) return
        run = (error)=>{
            this._status = this.REJECTED;
            this._value = err;
            let ck;
            while(ck = this.rejectedQueue.shift()){
                ck(error)
            }
        }
        setTimeout(run,0);
    };

    // 最重要的then函數:
    // 特性1-用於註冊回調,then()函數有兩個參數,都是可選的,若是參數不是函數將會被忽略
    // 同一個Promise能夠屢次註冊then(),特性2-因此Promise內部要維護兩個數組,分別存儲then上註冊的成功回調和失敗回調);
    // then()支持鏈式調用,特性3-之因此支持是由於其返回值是一個新的Promise,此處要完成回調函數onFulfilled穿透機制的實現、錯誤冒泡機制的實現
    
    //特性1-回調函數的註冊:故爲它傳遞兩個回調函數佔位
    then(onFulfilled,onRejected){
        const {_status, _value} = this;
        //特性3-返回一個新的promise
        return new MyPromise((onFulfilledNext, onRejectedNext)=>{
            let fulfilled = value => {
                try{
                    if(typeof onFulfilled != "function"){
                        //若是 onFulfilled 或 onRejected 不是函數,onFulfilled 或 onRejected 被忽略
                        onFulfilledNext(value)
                    }else{
                        //若是 onFulfilled 或者 onRejected 返回一個值 res
                        let res = onFulfilled(value)
                        if(res instanceof MyPromise){
                            //若 res 爲 Promise ,這時後一個回調函數,就會等待該 Promise 對象(即res )的狀態發生變化,纔會被調用,而且新的 Promise 狀態和 res 的狀態相同
                            res.then(onFulfilledNext, onRejectedNext)
                        }else{
                            //若 res 不爲 Promise ,則使res 直接做爲新返回的 Promise 對象的值
                            onFulfilledNext(res)
                        }
                    }
                }catch(err){
                    onRejectedNext(err)
                }
            }
            let rejected = error => {
                try{
                    if(typeof onRejected != "function"){
                        onRejectedNext(error)
                    }else{
                        let res = onRejectedNext(error)
                        if(res instanceof MyPromise){
                            res.then(onFulfilledNext, onRejectedNext)
                        }else{
                            onFulfilledNext(res);
                        }
                    }
                }catch(err){
                    onRejectedNext(err)
                }
            }
            switch(_status){
                case PENDING:
                    //在 promise 狀態改變前, onFulfilled或者onRejected不可被調用
                    this._fulfilledQueue.push(onFulfilled)
                    this._rejectedQueue.push(onRejected)
                    break
                case FULFILLED:
                    //onFulfilled調用次數只有一次,fulfilled對onFulFilled進行包裝。why need 包裝?由於onFulFilled多是函數、可能不是,若是是函數,返回值多是Promise可能不是
                    fulfilled(_value)
                    break
                case REJECTED:
                    //onRejected調用次數只有一次
                    rejected(_value)
                    break
            }
        })
    }
}

第三版

class MyPromise {
    constructor(handle) {
        if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []
        try {
            handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {
            this._reject(err)
        }
    }
    // 添加resovle時執行的函數
    _resolve(val) {
        const run = () => {
            if (this._status !== PENDING) return
            const runFulfilled = (value) => {
                let cb;
                while (cb = this._fulfilledQueues.shift()) {
                    cb(value)
                }
            }
            const runRejected = (error) => {
                let cb;
                while (cb = this._rejectedQueues.shift()) {
                    cb(error)
                }
            }
            /* 
              若是resolve的參數爲Promise對象,則必須等待該Promise對象狀態改變後,
              當前Promsie的狀態纔會改變,且狀態取決於參數Promsie對象的狀態,
              所以這裏進行邏輯區分
            */
            if (val instanceof MyPromise) {
                val.then(value => {
                    this._value = value
                    this._status = FULFILLED
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    this._status = REJECTED
                    runRejected(err)
                })
            } else {
                this._value = val
                this._status = FULFILLED
                runFulfilled(val)
            }
        }
        setTimeout(run, 0)
    }
    _reject(err) {
        if (this._status !== PENDING) return
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {
                cb(err)
            }
        }
        setTimeout(run, 0)
    }
    then(onFulfilled, onRejected) {
        const { _value, _status } = this
        return new MyPromise((onFulfilledNext, onRejectedNext) => {
            let fulfilled = value => {
                try {
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(value)
                    } else {
                        let res = onFulfilled(value);
                        if (res instanceof MyPromise) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            let rejected = error => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error)
                    } else {
                        let res = onRejected(error);
                        if (res instanceof MyPromise) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }
        })
    }
}

第四版 finally

class MyPromise {
    constructor(handle) {
        if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []
        try {
            handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {
            this._reject(err)
        }
    }
    _resolve(val) {
        const run = () => {
            if (this._status !== PENDING) return
            const runFulfilled = (value) => {
                let cb;
                while (cb = this._fulfilledQueues.shift()) {
                    cb(value)
                }
            }
            const runRejected = (error) => {
                let cb;
                while (cb = this._rejectedQueues.shift()) {
                    cb(error)
                }
            }
            if (val instanceof MyPromise) {
                val.then(value => {
                    this._value = value
                    this._status = FULFILLED
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    this._status = REJECTED
                    runRejected(err)
                })
            } else {
                this._value = val
                this._status = FULFILLED
                runFulfilled(val)
            }
        }
        setTimeout(run, 0)
    }
    _reject(err) {
        if (this._status !== PENDING) return
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {
                cb(err)
            }
        }
        setTimeout(run, 0)
    }
    then(onFulfilled, onRejected) {
        const { _value, _status } = this
        return new MyPromise((onFulfilledNext, onRejectedNext) => {
            let fulfilled = value => {
                try {
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(value)
                    } else {
                        let res = onFulfilled(value);
                        if (res instanceof MyPromise) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            let rejected = error => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error)
                    } else {
                        let res = onRejected(error);
                        if (res instanceof MyPromise) {
                            res.then(onFulfilledNext, onRejectedNext)
                        } else {
                            onFulfilledNext(res)
                        }
                    }
                } catch (err) {
                    onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }
        })
    }
    // 添加catch方法
    catch(onRejected) {
        return this.then(undefined, onRejected)
    }
    // 添加靜態resolve方法
    static resolve(value) {
        // 若是參數是MyPromise實例,直接返回這個實例
        if (value instanceof MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }
    // 添加靜態reject方法
    static reject(value) {
        return new MyPromise((resolve, reject) => reject(value))
    }
    // 添加靜態all方法
    static all(list) {
        return new MyPromise((resolve, reject) => {
            /**
             * 返回值的集合
             */
            let values = []
            let count = 0
            for (let [i, p] of list.entries()) {
                // 數組參數若是不是MyPromise實例,先調用MyPromise.resolve
                this.resolve(p).then(res => {
                    values[i] = res
                    count++
                    // 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
                    if (count === list.length) resolve(values)
                }, err => {
                    // 有一個被rejected時返回的MyPromise狀態就變成rejected
                    reject(err)
                })
            }
        })
    }
    // 添加靜態race方法
    static race(list) {
        return new MyPromise((resolve, reject) => {
            for (let p of list) {
                // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變
                this.resolve(p).then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            }
        })
    }
    finally(cb) {
        return this.then(
            value => MyPromise.resolve(cb()).then(() => value),
            reason => MyPromise.resolve(cb()).then(() => { throw reason })
        );
    }
}

其它問題

1. Promise與宏觀任務、async函數等執行順序問題

Promise是微任務的一種實現,給出以下的代碼,分析其輸出順序編程

//題目1
async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}
console.log('script start')
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
    console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})
promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')
//題目2
async function async1() {
    console.log('async1 start');
    await async2();
    await async3()
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
async function async3() {
    console.log('async3');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
//題目3
async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
  }
async function async2(){
    console.log('async2')
}
console.log('script start')
setTimeout(function(){
    console.log('setTimeout0') 
},0)  
setTimeout(function(){
    console.log('setTimeout3') 
},3)  
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
    console.log('promise2')
}).then(function(){
    console.log('promise3')
})
console.log('script end')

答案:
題目一:script start->a1 start->a2->promise2->script end->promise1->a1 end->promise2.then->promise3->setTimeout
題目二:script start->async1 start->async2->promise1->script end->async3->promise2->async1 end->setTimeout
在瀏覽器console可實驗
題目三:script start->async1 start->async2->promise1->promise2
->script end->nextTick->async1 end->promise3->setTimeout->setImmediate->setTimeout3
在node環境中可實驗api

2. 如何實現all、如何實現鏈式調用的

all()的實現:函數中維護了一個數組和計數器,數組的大小爲初始時all函數中傳遞的Promise對象數量,數組存儲各個Promise執行成功後resolve獲得的結果,每成功一個計數器+1,直到計數器累加到數組大小時即調用resolve(value),只要有一個Promise執行到失敗的回調,即所有失敗。
鏈式調用的實現:then函數返回的依然是一個Promise,見第二版的Promise實現。數組

3. all中任何一個Promise出錯,致使其它正確的數據也沒法使用,如何解決呢?

方法1:使用allSettled替代;方法2:改寫Promise,將reject操做換成是resolve(new Error("自定義的錯誤"));方法3:引入第三方庫promise-transactionpromise

4.Promise真的好用嗎,它存在什麼問題?

問題1:沒有提供中途取消的機制;問題2:必需要設置回調,不然內部錯誤沒法在外部反映出來;問題3:使用時依然存在大量Promise的api,邏輯不清晰。瀏覽器

相關文章
相關標籤/搜索