深度promise && 自實(shí)現(xiàn)一個(gè)Promise對(duì)象

什么是promise?

promise是js直接中進(jìn)行異步編程的新的解決方案

promise的基本使用

Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是 resolve和reject 。它們是兩個(gè)函數(shù),由JavaScript引擎提供,不用自己部署。

創(chuàng)建一個(gè)promise
var p = new Promise((resolve,reject) => {
  // 執(zhí)行器函數(shù)
  1、成功 調(diào)用resolve(value)
  2、失敗 調(diào)用reject(resaon)
})
p.then(
    value => { // 接收得到的成功的value數(shù)據(jù)
  },
    reason => { // 接收得到的失敗的reason數(shù)據(jù)
  }
)
promise的三種狀態(tài):ending 、resolved(又稱Fulfilled)、rejected
  • promise 對(duì)象初始化狀態(tài)為 pending
  • 當(dāng)調(diào)用resolve(成功),會(huì)由pending => resolved
  • 當(dāng)調(diào)用reject(失敗),會(huì)由pending => rejected
    注:promise一旦狀態(tài)改變,就不會(huì)再改,

一個(gè)promise指定多個(gè)成功/失敗的回調(diào)函數(shù),當(dāng)promise改變?yōu)閷?duì)應(yīng)狀態(tài)時(shí)都會(huì)調(diào)用

        var p = new Promise((resolve,reject) => {
            setTimeout(() => {
                console.log('上課了')
                resolve()
            },10)
        })    
        p.then(() => {
            console.log('班長(zhǎng):起立')
        })
        p.then(() => {
            console.log('同學(xué)們:老師好')
        })
改變promise狀態(tài)與指定回調(diào)函數(shù)誰(shuí)先誰(shuí)后?
  • 都有可能 正常情況下是先指定回調(diào)再改變狀態(tài),但也可以先改變狀態(tài)再指定回調(diào)
  • 如何先改變狀態(tài),再指定回調(diào)
    在執(zhí)行器函數(shù)中直接調(diào)用resolve/reject,延遲更長(zhǎng)時(shí)間才調(diào)用then()
  • 什么時(shí)候才能得到數(shù)據(jù)?
    如果先指定回調(diào),當(dāng)狀態(tài)改變時(shí),回調(diào)就會(huì)先調(diào)用,得到數(shù)據(jù)
    如果先改變狀態(tài),那當(dāng)指定回調(diào)時(shí),回調(diào)函數(shù)就會(huì)調(diào)用,得到數(shù)據(jù)
        // 先指定回調(diào)函數(shù),后改變狀態(tài)
        var p = new Promise((resolve,reject) => {
            setTimeout(() => {
                // 后改變狀態(tài),同時(shí)傳遞數(shù)據(jù),異步執(zhí)行回調(diào)函數(shù)
                resolve('成功')
            },3000)
        }).then((value) => {
            // 先指定回調(diào)函數(shù),保存當(dāng)前指定的回調(diào)函數(shù)
            console.log('我是回調(diào)函數(shù)')
            console.log(value)
        })

        // 先改變狀態(tài),后指定回調(diào)函數(shù)
        var p = new Promise((resolve, reject) => {
            // 先改變狀態(tài),同時(shí)傳遞數(shù)據(jù)
            resolve('馬上就成功') 
        })
        setTimeout(() => {
            // 后指定回調(diào)函數(shù),異步執(zhí)行回調(diào)函數(shù)
            p.then((value) => {
                console.log(value)
            })
        },1000)

Promise.prototype.then()

Promise實(shí)例生成以后,可以用then方法分別指定Resolved狀態(tài)和Reject狀態(tài)的回調(diào)函數(shù)。then 方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)镽esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)?Reject時(shí)調(diào)用。其中,第二個(gè)函數(shù)是可選的,不一定要提供。]

then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛?,即then方法后面再調(diào)用另一個(gè)then方法。

采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)。這時(shí),前一個(gè)回調(diào)函數(shù),有可能返回的還是一個(gè)Promise對(duì)象(即有異步操作),這時(shí)后一個(gè)回調(diào)函數(shù),就會(huì)等待該P(yáng)romise對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用。

promise.then()返回新的peomise的結(jié)果狀態(tài)由前一個(gè)指定的回調(diào)函數(shù)執(zhí)行的結(jié)果決定:

  • 如果拋出異常,新promise變?yōu)閞esolved ,reason為拋出的異常
  • 如果返回的是非promise的任意值,新peomise變?yōu)閞esolved,value為返回的值
  • 如果返回的是另一個(gè)新的peomise,此promise的結(jié)果會(huì)成為新的promise的結(jié)果
        var p = new Promise((resolve,reject) => {
                resolve('成功')
        })
        .then(
            value => { 
                //  沒(méi)有指定返回值,是一個(gè)非promise的任意值
                console.log('resolve1',value) 
            },reason => {
                console.log('reject1', reason) 
            })
        .then(
            value => {
                // 根據(jù)上一個(gè)函數(shù)的結(jié)果,狀態(tài)為resolved, 調(diào)用
                console.log('resolve2', value) 
            }, reason => {
                console.log('reject2', reason) 
            })
        // resolve1 成功
        // resolve2 undefined  
        var p = new Promise((resolve,reject) => {
                resolve('成功')
        })
        .then(
            value => { 
              //  根據(jù)執(zhí)行器函數(shù)結(jié)果,調(diào)用成功函數(shù)
                console.log('resolve1',value) 
                throw '失敗了'
            },reason => {
                console.log('reject1', reason) 
            })
        .then(
            value => {
                console.log('resolve2', value) 
            }, reason => {
            // 根據(jù)上一個(gè)then的函數(shù)返回結(jié)果,調(diào)用reject,reason為錯(cuò)誤原因
                console.log('reject2', reason) 
            })

        // resolve1 成功
        // reject2 失敗了

如何中斷promise鏈?

  • 當(dāng)使用promise的then鏈?zhǔn)秸{(diào)用時(shí),在中間中斷,不再調(diào)用后面的回調(diào)函數(shù)
    辦法:在回調(diào)函數(shù)中返回一個(gè)pendding狀態(tài)的promise對(duì)象
        new Promise((resolve, reject) => {
            reject(1)
        }).then(
            value => console.log(value)
        ).catch(
            reason => {
                console.log('程序報(bào)錯(cuò)就中斷promise')
                return new Promise(() => { }) // 返回一個(gè)pending的promise
            })
        .then(
            value => console.log('如果程序報(bào)錯(cuò),將不會(huì)執(zhí)行到這')
        )

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。

如果Promise狀態(tài)已經(jīng)變成Resolved,再拋出錯(cuò)誤是無(wú)效的。

如果沒(méi)有使用catch方法指定錯(cuò)誤處理的回調(diào)函數(shù),Promise對(duì)象拋出的錯(cuò)誤不會(huì)傳遞到外層代碼,即不會(huì)有任何 反應(yīng)。

Promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說(shuō),錯(cuò)誤總是會(huì)被下一個(gè)catch語(yǔ)句捕獲。

catch方法返回的還是一個(gè)Promise對(duì)象,因此后面還可以接著調(diào)用then方法。

鏈?zhǔn)秸{(diào)用中,如果沒(méi)有報(bào)錯(cuò),會(huì)跳過(guò)catch方法,繼續(xù)后面的then()方法,要是then方法里面報(bào)錯(cuò),就與前面的catch無(wú)關(guān)了。

        var p = new Promise((resolved,reject) => {
            reject('失敗了')
            resolved('你看我在哪?')  // 永遠(yuǎn)不會(huì)被調(diào)用
        }).then(( value ) => {
            console.log(1)
        }).then((value) => { 
            console.log(2)
        }).catch((error) => {
            // 處理前面三個(gè)Promise產(chǎn)生的錯(cuò)誤
            console.log(error)
        }).then(() => {
            console.log('我是可以被調(diào)用的')
        });

return 一個(gè) error 對(duì)象并不會(huì)拋出錯(cuò)誤,所以不會(huì)被后續(xù)的 .catch 捕獲

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

// then: Error: error!!!
//    at Promise.resolve.then (...)
//    at ...

Promise.all()

Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。且返回一個(gè)promise

p 的狀態(tài)由 p1 、p2、p3決定,分成兩種情況。

  • 只有 p1 、p2、p3的狀態(tài)都變成 fulfilled, p 的狀態(tài)才會(huì)變成fulfilled ,此時(shí) p1 、p2、p3 的返回值組成一個(gè)數(shù)組,傳遞給 p 的回調(diào)函數(shù)。

  • 只要p1 、p2、p3之中有一個(gè)被rejected, p 的狀態(tài)就變成rejected ,此時(shí)第一個(gè)被 reject實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。

    let p1 = new Promise((resolve, reject) => {
        resolve(1);
    });

    let p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);            
        },100)
    });

    let p3 = new Promise((resolve, reject) => {
        resolve(3);
    });

    Promise.all([p1, p2, p3])
        .then(data => {
            console.log(data);
            // [1, 2, 3] 結(jié)果順序和promise實(shí)例數(shù)組順序是一致的
        })
        .catch(error => {
            console.log(data);
        })

Promise.race()

Promise.race()是參數(shù)中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變,那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。

Promise.race()方法,同樣是將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。

    let p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 100)
    });

    let p2 = new Promise((resolve, reject) => {
        resolve(2);
    });

    let p3 = new Promise((resolve, reject) => {
        resolve(3);
    });

    Promise.race([p1, p2, p3])
        .then(data => {
            console.log(data);
            // 2 返回那個(gè)率先改變的Promise實(shí)例的返回值
        })
        .catch(error => {
            console.log(data);
        })

Promise.resolve()

主要作用:將現(xiàn)有對(duì)象轉(zhuǎn)為Promise對(duì)象

Promise.resolve 方法的參數(shù)分成四種情況

  • 參數(shù)是一個(gè)Promise實(shí)例
    如果參數(shù)是Promise實(shí)例,那么Promise.resolve將不做任何修改、原封不動(dòng)地返回這個(gè)實(shí)例

  • 參數(shù)是一個(gè)thenable對(duì)象
    thenable對(duì)象指的是具有then方法的對(duì)象,Promise.resolve方法會(huì)將這個(gè)對(duì)象轉(zhuǎn)為Promise對(duì)象,然后就立即執(zhí)行thenable對(duì)象的then方法。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
   }
  };

let p1 = Promise.resolve(thenable); 
p1.then(function(value) {
  console.log(value);  // 42 
});
  • 參數(shù)不是具有then方法的對(duì)象,或者根本不是對(duì)象
    如果參數(shù)是一個(gè)原始值,或者是一個(gè)不具有then方法的對(duì)象,則Promise.resolve方法返回一個(gè)新的Promise對(duì)象,狀態(tài)為Resolved。
var p = Promise.resolve('Hello');
p.then(function (s){ 
  console.log(s)
});
// Hello
  • 不帶有任何參數(shù)
    Promise.resolve方法允許調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)Resolved狀態(tài)的Promise對(duì)象
var p = Promise.resolve();
p.then(function () {
     // ...
});

需要注意的是,立即resolve的Promise對(duì)象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時(shí),而不是在下一輪“事件循環(huán)”的開始時(shí)。

    setTimeout(function () {
        console.log('three');
    }, 0);
    Promise.resolve().then(function () {
        console.log('two');
    });
    console.log('one');
    // one 
    // two
    // three

Promise.reject()

Promise.reject(reason)方法也會(huì)返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected。它的參數(shù)用法與Promise.resolve方法完全一致。

實(shí)現(xiàn)一個(gè)基本的promise

// 自定義promise函數(shù)模塊:IIFE
(function (window) {
    const PENDING = 'pending'
    const RESOLVED = 'resolved'
    const REJECTED = 'rejected'
    // promise構(gòu)造函數(shù)
    // excutor執(zhí)行器函數(shù)(同步執(zhí)行)
    function Promise(excutor) {
        var self = this;
        this.status = PENDING  // 給promise對(duì)象指定status屬性,初始值為pending
        this.data = undefined // 給promise對(duì)象指定一個(gè)用戶存儲(chǔ)結(jié)果數(shù)據(jù)的屬性
        this.callbacks = [] // 每個(gè)元素的結(jié)構(gòu){ onResolved(){}, onReject(){}}
        // 立即同步執(zhí)行excutor
        function resolve(value) {
            // 如果當(dāng)前狀態(tài)不是pending,直接結(jié)束
            if (self.status !== PENDING) {
                return 
            }
            // 將狀態(tài)改為resolved
            self.status = RESOLVED
            // 保存value數(shù)據(jù)
            self.data = value
            // 如果有待執(zhí)行的callback函數(shù),立即異步執(zhí)行回調(diào)函數(shù)onResolved
            if (self.callbacks.length > 0) {
                // 異步執(zhí)行
                setTimeout(() => {
                    self.callbacks.forEach(callbacksObj => {
                            callbacksObj.onResolved(value)
                    })                    
                })

            }
        }
        function reject(reason) {
            // 如果當(dāng)前狀態(tài)不是pending,直接結(jié)束
            if (self.status !== PENDING) {
                return
            }
            // 將狀態(tài)改為resolved
            self.status = REJECTED
            // 保存value數(shù)據(jù)
            self.data = reason
            // 如果有待執(zhí)行的callback函數(shù),立即異步執(zhí)行回調(diào)函數(shù)onRejected
            if (self.callbacks.length > 0) {
                // 異步執(zhí)行
                setTimeout(() => {
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onRejected(reason)
                    })                    
                })

            }
        }
        try { 
            excutor(resolve, reject)
        } catch(error){
            // 如果執(zhí)行器出現(xiàn)異常,promise對(duì)象變?yōu)閞eject狀態(tài)
            reject(error)
        }    
    }


    // promise原型對(duì)象的then方法
    // 指定成功和失敗的回調(diào)函數(shù)
    // 返回一個(gè)新的promise對(duì)象
    Promise.prototype.then = function (onResolved, onRejected) {
        // 向后傳遞成功的value
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        // 指定默認(rèn)的失敗的回調(diào)(實(shí)現(xiàn)錯(cuò)誤/異常穿透的關(guān)鍵點(diǎn)) 向后傳遞失敗的reason
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

        const self = this
        // console.log(self.data)
        return new Promise((resolve, reject) => {
            // 調(diào)用指定的函數(shù)處理
            // 根據(jù)執(zhí)行結(jié)果,改變r(jià)eturn的promise的狀態(tài)
            function handle(callback) {
                    // 1、如果拋出異常,return的promise就會(huì)失敗,reason就是error
                    // 2、如果回調(diào)函數(shù)返回非promise,return的promise就會(huì)成功,value就是返回值
                    // 3、如果回調(diào)函數(shù)返回的是promise,return的promise結(jié)果就是這個(gè)promise的結(jié)果
                try {
                        const result = callback(self.data)
                        // 3、如果回調(diào)函數(shù)返回的是promise,return的promise結(jié)果就是這個(gè)promise的結(jié)果
                        if (result instanceof Promise) {
                            // result.then(
                            //     value => {
                            //         resolve(value) // 當(dāng)result成功時(shí),return的promise就會(huì)成功,value就是返回的值
                            //     },
                            //     reason => {
                            //         reject(reason) // 當(dāng)result失敗時(shí),讓return的promise也失敗
                            //     }
                            // )
                            // 或者直接
                             result.then(resolve,reject)
                        } else {
                            // 2、如果回調(diào)函數(shù)返回非promise,return的promise就會(huì)成功,value就是返回值
                            resolve(result)
                        }
                    } catch (error) {
                        // 1、如果拋出異常,return的promise就會(huì)失敗,reason就是error
                        reject(error)
                    }
                                    
            }

            if (self.status === PENDING) {
                // 當(dāng)前狀態(tài)還是peding狀態(tài),將回調(diào)函數(shù)保存起來(lái)
                self.callbacks.push({
                    onResolved(value) {
                        handle(onResolved)
                    },
                    onRejected(reason) {
                        handle(onRejected)
                    }
                })            
            } else if (self.status === RESOLVED) {
                // 如果當(dāng)前是resolved 異步執(zhí)行onResolved并改變r(jià)eturn的promise狀態(tài)
                setTimeout(() => {
                    // handle(onRejected)
                    handle(onResolved)
                })
            } else { // reject
                // 如果當(dāng)前是resolved 異步執(zhí)行onRejectd并改變r(jià)eturn的promise狀態(tài)
                setTimeout(() => {
                    handle(onRejected)
                })
            }            
        })
    }
    // promise原型對(duì)象的catch方法
    // 指定失敗的回調(diào)函數(shù)
    // 返回一個(gè)新的promise對(duì)象
    Promise.prototype.catch = function (onRejected) {
        return this.then(undefined,onRejected)
    }

    
    // promise函數(shù)對(duì)象的resolve方法
    // 返回一個(gè)指定結(jié)果的成功的promise
    Promise.resolve = function (value) {
        // 如果是一般值
        // 如果是成功的promise
        // 如果是失敗的promise
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then(resolve, reject)
            } else {
                // value不是promise =》 promise 變成功,數(shù)據(jù)是value
                resolve(value)
            }
        })
    }
    // promise函數(shù)對(duì)象的reject方法
    // 返回一個(gè)指定reason的成功的promise
    Promise.reject = function (reason) {
        return new Promise((resolve, reject) => {
            reject(reason)
        })
    }
    // promise函數(shù)對(duì)象的all方法
    // 返回一個(gè)promise,只有當(dāng)所有的promise都成功時(shí)才算成功,否則失敗
    Promise.all = function (promises) {
        // 用來(lái)保存所有成功value數(shù)組
        const values = new Array(promises.length)
        // 計(jì)數(shù)器
        let resolvedCount = 0
        return new Promise((resolve, reject) => {
            // 遍歷獲取每一個(gè)promise的結(jié)果
            promises.forEach((p, index) => {
                Promise.resolve(p).then(
                    value => {
                        resolvedCount++
                        // p成功,將成功的value保存values
                        value[index] = value
                        // 如果全部成功了,將return的promise改變成功
                        if (resolvedCount === promises.length) {
                            resolve(values)
                        }
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }
    
    // promise函數(shù)對(duì)象的race方法
    // 返回一個(gè)promise,其結(jié)果由第一個(gè)完成的promise決定
    Promise.race = function (promises) {
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                // Promise.resolve(p)為了兼容傳入的不是一個(gè)promise
                Promise.resolve(p).then(
                    value => {
                        resolve(value)    
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }
    // 返回一個(gè)promise對(duì)象,在指定的時(shí)間后再確認(rèn)結(jié)果
    Promise.resolveDelay = function (value,time) {
        return new Promise((resolve, reject) => { 
            setTimeout(() => {
                // value是promise
                if (value instanceof Promise) {
                    value.then(resolve, reject)
                } else {
                    // value不是promise =》 promise 變成功,數(shù)據(jù)是value
                    resolve(value)
                }
            }, time)
        })
    }
    Promise.rejectDelsy = function (reason, time) {
        return new Promise((resolve, reject) => { 
            setTimeout(() => {
                reject(value)
            }, time)
        })
    }
    // 向外暴露promise函數(shù)
    window.Promise = Promise
})(window)

關(guān)于promise的經(jīng)典面試題

選了幾篇有質(zhì)量的promise面試題,可以做做:
1、Promise面試題
2、Promise 必知必會(huì)(十道題)
3、面試官眼中的Promise

本文參考:
《es6標(biāo)準(zhǔn)教程》
《promise核心技術(shù)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:http://es6.ruanyifeng.com/#docs/promise Promise 的含義 ...
    AI云棧閱讀 978評(píng)論 0 7
  • //本文內(nèi)容起初摘抄于 阮一峰 作者的譯文,用于記錄和學(xué)習(xí),建議觀者移步于原文 概念: 所謂的Promise,...
    曾經(jīng)過(guò)往閱讀 1,320評(píng)論 0 7
  • Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案 —— 回調(diào)函數(shù)和事件 —— 更合理且強(qiáng)大 Promis...
    了凡和纖風(fēng)閱讀 565評(píng)論 0 1
  • JavaScript里通常不建議阻塞主程序,尤其是一些代價(jià)比較昂貴的操作,如查找數(shù)據(jù)庫(kù),下載文件等操作,應(yīng)該用異步...
    張歆琳閱讀 2,831評(píng)論 0 12
  • 現(xiàn)今社會(huì)人人手里拿著手機(jī) ,你看吧:老頭津津有味瞇者眼睛瞪著“惠頭條”在掙金幣;兒子盯著“王者榮耀”賭輸贏;孫子全...
    海上有仙山閱讀 223評(píng)論 1 3

友情鏈接更多精彩內(nèi)容