天天看點

從如何使用到如何實作一個Promise

這篇文章我們一起來學習如何使用`Promise`,以及如何實作一個自己的`Promise`,講解非常清楚,全程一步一步往後實作,附帶詳細注釋與原理講解。

前言

這篇文章我們一起來學習如何使用

Promise

,以及如何實作一個自己的

Promise

,講解非常清楚,全程一步一步往後實作,附帶詳細注釋與原理講解。

如果你覺的這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公衆号首發,關注 前端南玖 第一時間擷取最新的文章~

promise是什麼?主要用來解決什麼問題?

Promise是異步程式設計的一種解決方案,比傳統解決方案--回調函數和事件--更合理更強大。

Promise

特點:

(1)對象的狀态不受外界影響。

Promise

對象代表一個異步操作,有三種狀态:

pending

(進行中),

fulfilled

(已成功)和

reject

(已失敗)。隻有異步操作的結果,可以決定目前是哪一種狀态,任何其它操作都無法改變這個狀态。這也是

Promise

(承諾)這個名字的由來。

(2)一旦狀态改變,就不會再變,任何時候都可以得到這個結果。

Promise

對象的狀态改變,隻有兩種可能:從

pending

變為

fulfilled

和從

pending

rejected

promise主要用來解決:

  • 回調地獄
  • 并發請求
  • 異步方案優化(但它本身不是異步的,new Promise()後,它會立即執行)

promise基本用法

ES6規定,

Promise

對象是一個構造函數,用來生成

Promise

執行個體

下面代碼創造了一個

Promise

const promise = new Promise(function(resolve,reject){
    //...
    if(/*異步操作成功*/){
       resolve(value)
    }else{
        //異步操作失敗
        reject(error)
    }
})
           

Promise

構造函數接受一個函數作為參數,該函數的兩個參數分别是

resolve

reject

.他們是兩個函數,由JavaScript引擎提供,不用自己部署。

resolve

函數的作用是,将

Promise

對象的狀态從“未完成”變成“成功”(即從pending變為resolve),在異步操作成功時調用,并将異步操作的結果,作為參數傳遞出去;

reject

Promise

對象的狀态從“未完成”變成“失敗”(即從pending變為rejected),在異步操作失敗時調用,并将異步操作報出的錯誤,作為參數傳遞出去。

Promise

執行個體生成以後,可以用

then

方法分别指定

resolved

狀态和

rejected

狀态的回調函數,或用

catch

方法指定

rejected

狀态的回調函數。

promise.then(res=>{
    //success
},error=>{
    //error
}).catch(err=>{})
           

then

方法可以接受兩個回調函數作為參數,第一個回調函數是

Promise

對象的狀态變為

resolved

時調用,第二個回調函數是

Promise

rejected

時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接收

Promise

對象傳出的值作為參數。

Ok,通過上面對promise基本用法的描述,我們大概知道了一個promise類裡面都應該包含哪些内容了:

  • promise狀态:pending,fulfilled,rejected
  • promise傳回值
  • 執行器:promise執行入口(也就是你傳入的那個函數)
  • resolve:改變promise狀态為fulfilled
  • reject:改變promise狀态為rejected
  • then:接收兩個回調,onFulfilled, onRejected。分别在promise狀态變為fulfiled或rejected後執行
  • catch:接受一個回調,在promise狀态變為rejected後執行

簡單實作一個promise

我們知道了一個promise内容至少包含以上那些内容,是以一個簡單的promise内部至少是這樣的

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise狀态
        this.promiseRes = null  // promise傳回值
       	const resolve = result=>{
        //...
        }
        const reject = result=>{
           //...
        }
        try{
            init(resolve,reject)  // init就是初始化執行器
        }catch(err){
            reject(err)
        }
        
    }
    then(onFulfilled,onRejected){
       //...
    }
  	catch(onRejected){
      //...
    }
}
           

OK,大概了解之後,我們再來一個一個的看裡面每個部分的實作以及作用

Promise的執行器

它其實是我們在

new Promise

時傳入的一個回調函數,這個函數本身是同步的,也就是說在

new Promise

時它就會執行,這也是我們操作promise的入口。

class myPromise{
  //...
  constructor(init){
        try{
            init(resolve,reject)  // init就是初始化執行器
        }catch(err){
            reject(err) //這裡主要是在init執行器函數出錯時,用以讓promise狀态變為rejected
        } 
    }
  //...
}
           

該函數接受兩個回調函數(resolve,reject)作為參數,用以改變Promise的狀态

resolve與reject方法

這兩個函數作為參數傳到執行器函數中,用以後續改變Promise狀态

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise狀态
        this.promiseRes = null  // promise傳回值
        this.resolveCallback = [] //成功回調集合
        this.rejectCallback = [] //失敗回調集合
        const resolve = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd //改變狀态
                this.promiseRes = result //傳回值
                //依次調用成功回調
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED //改變狀态
                this.promiseRes = result //傳回值
                // 依次調用失敗回調
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{
            init(resolve,reject)  // 注意this指向
        }catch(err){
            reject(err)
        }
        
    }
}
           

初步then方法

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise狀态
        this.promiseRes = null  // promise傳回值
        this.resolveCallback = [] //成功回調集合
        this.rejectCallback = [] //失敗回調集合
        const resolve = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd //改變狀态
                this.promiseRes = result //傳回值
                //依次調用成功回調
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED //改變狀态
                this.promiseRes = result //傳回值
                // 依次調用失敗回調
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{
            init(resolve,reject)  // 注意this指向
        }catch(err){
            reject(err)
        }
        
    }
    then(onFulfilled,onRejected){
        if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
            onFulfilled(this.promiseRes)
        }
        if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
            onRejected(this.promiseRes)
        }
    }
}
           

寫到這裡,我們的promise已經初步成型了,我們可以來測試一下:

const res1 = new myPromise((res,rej)=>{
    res('成功啦~')
    rej('失敗啦~')
})
res1.then((res)=>{
    console.log(res)
},err=>{
    console.log(err)
})
// 按照預期,這裡應該是隻會列印出成功啦~
           
從如何使用到如何實作一個Promise

從上圖看我們,是不是符合我們的預期,并且

myPromise

内部與原生的

Promise

也是非常相似的。你們是不是覺得這裡已經沒問題了,上面我們隻是測了一下同步方法的執行,但别忘了,Promise主要是來解決異步問題的,我們再來試一下裡面執行異步方法還符不符合我們的預期?

const res1 = new myPromise((res,rej)=>{
    setTimeout(()=>res('成功啦~'),1000)
    // rej('失敗啦~')
})
res1.then((res)=>{
    console.log(res)
})
           

這裡我們預期本來是一秒之後列印成功啦,但它并沒有如我們所願,反而是什麼也沒列印出來,這是因為在setTimeout執行之前(pen ding)這個then方法已經執行過了,1s後狀态變成fulfilled時,then也不會再執行了。

是以我們需要保證then方法的回調函數在promise狀态變成

fulfilled

rejected

時再執行,那麼當promise狀态為

pending

時我們先要把回調存在對應的隊列中,等後續狀态改變後再執行

較完整then方法

OK,這裡我們修改一下我們的then方法,讓其保證異步代碼執行的正确性 (具體實作微任務我們可以用 mutationObserver,這裡我們就用setTimeout來模拟一下)

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise狀态
        this.promiseRes = null  // promise傳回值
        this.resolveCallback = [] //成功回調集合
        this.rejectCallback = [] //失敗回調集合
        const resolve = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd //改變狀态
                this.promiseRes = result //傳回值
                //依次調用成功回調
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED //改變狀态
                this.promiseRes = result //傳回值
                // 依次調用失敗回調
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{
            init(resolve,reject)  // 注意this指向
        }catch(err){
            reject(err)
        }
        
    }
    then(onFulfilled,onRejected){
        if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
            onFulfilled(this.promiseRes)
        }
        if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
            onRejected(this.promiseRes)
        }
        if(this.state === myPromise.PENDING){
            if(onFulfilled && typeof onFulfilled === 'function'){
                this.resolveCallback.push(()=>
                // 這裡我們用setTimeout來模拟實作then的微任務
                setTimeout(()=>{
                    onFulfilled(this.promiseRes)
                },0)
                )
            }
            if(onRejected && typeof onRejected === 'function'){
                this.rejectCallback.push(()=>
                // 這裡我們用setTimeout來模拟實作then的微任務
                setTimeout(()=>{
                    onRejected(this.promiseRes)
                },0)
                )
            }
        }
    }
}
           

這裡我們可以再測試一下上面那個異步函數的測試用例,發現它能夠正确列印,OK,一個較完整的then方法就算實作了~

then的鍊式調用

then

方法會傳回一個新的

Promise

(⚠️注意:不是原來的那個

Promise

)是以可以采用鍊式調用

采用鍊式的

then

,可以指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能傳回的還是一個

Promise

對象(即有異步操作),這時後一個回調函數,就會等待該

Promise

對象的狀态發生變化,才會被調用。

then(onFulfilled,onRejected){
        const {promiseRes,state} = this
        let promise = new myPromise((reso,reje)=>{
            const resolveMyPromise = promiseRes => {
                try{
                    if(typeof onFulfilled !== 'function'){
                        // 如果then的第一個回調不是一個函數,直接忽略,傳回一個新的promise
                        reso(promiseRes)
                    }else{
                        // 擷取第一個回調的執行結果
                        const res = onFulfilled(promiseRes)
                        // 看該執行結果是否是一個promise
                        if(res instanceof myPromise){
                            // 是一個promise,等它狀态改變後再改變then傳回的promise狀态
                            res.then(reso,rej) 
                        }else{
                            // 不是一個promise,将它作為新的promise的resolve
                            reso(res)
                        }
                    }
                }catch(err){
                    //異常,直接将新的promise狀态置為rejected
                    reje(err)
                }
            }
            const rejectMyPromise = promiseRes => {
                try{
                    if(typeof onRejected !== 'function'){
                        // 如果then的第二個回調不是一個函數,直接忽略,傳回一個新的promise
                        reje(promiseRes)
                    }else{
                        // 擷取第二個回調的執行結果
                        const res = onRejected(promiseRes)
                        // 看該執行結果是否是一個promise
                        if(res instanceof myPromise){
                            // 是一個promise,等它狀态改變後再改變then傳回的promise狀态
                            res.then(reso,rej) 
                        }else{
                            // 不是一個promise,将它作為新的promise的resolve
                            reje(res)
                        }
                    }
                    
                }catch(err){
                    //異常,直接将新的promise狀态置為rejected
                    reje(err)
                }
            }
            if(state === myPromise.FULFILLEd) {
                resolveMyPromise(promiseRes)
            }
            if(state === myPromise.REJECTED) {
                rejectMyPromise(promiseRes)
            }
            if(state === myPromise.PENDING){
                if(onFulfilled && typeof onFulfilled === 'function'){
                    this.resolveCallback.push(()=>
                    // 這裡我們用setTimeout來模拟實作then的微任務
                    setTimeout(()=>{
                        resolveMyPromise(this.promiseRes)
                    },0)
                    )
                }
                if(onRejected && typeof onRejected === 'function'){
                    this.rejectCallback.push(()=>
                    // 這裡我們用setTimeout來模拟實作then的微任務
                    setTimeout(()=>{
                        rejectMyPromise(this.promiseRes)
                    },0)
                    )
                }
            }

        })
        return promise
    }
           

catch方法

我們知道then的第二個回調其實與catch方法是一樣的,是以catch方法我們可以這樣實作

catch(onRejected) {
        return this.then(undefined,onRejected)
    }
           

Promise.resolve

将對象轉為一個promise對象,根據參數不通可分為四種情況

  • 參數是一個Promise執行個體,直接傳回該執行個體
  • 參數是一個

    thenable

    對象,将該對象轉為Promise對象後,執行該對象的

    then

    方法
  • 沒有參數,也是傳回一個狀态為

    resolved

    的新的Promise對象
  • 參數是一個一個原始值,傳回一個新的Promise對象,狀态為

    resolved

手動實作:

static resolve(v){
  //1.參數是一個Promise執行個體,直接傳回
  if(v instanceof myPromise){
    return v
  }
  //2.參數是一個thenable對象,轉為Promise後執行該對象的then方法
  if(typeof v === 'object' && typeof v.then === 'function'){
    return new myPromise((res,rej)=>{
      v.then(res,rej)
    })
  }
  //3.沒有參數,直接傳回一個resolved狀态的promise
  if(!v){
    return new myPromise(res=>{
      res()
    })
  }
  //4.參數是一個原始值,傳回一個新的Promise,狀态為resolved
  return new myPromise(res=>{
    res(v)
  })
}
           

Promise.reject

傳回一個新的Promise對象,狀态為

rejected

static reject(v){
  return new myPromise((res,rej)=>{
    rej(v)
  })
}
           

Promise.all

該方法用于将多個Promise執行個體包裝成一個新的Promise執行個體,如果有不是Promise的項,則讓該項直接成功

用法:

const p = Promise.all([p1,p2,p3])
           

p

的狀态由

p1

p2

p3

決定,分成兩種情況。

(1)隻有

p1

p2

p3

的狀态都變成

fulfilled

p

的狀态才會變成

fulfilled

,此時

p1

p2

p3

的傳回值組成一個數組,傳遞給

p

的回調函數。

(2)隻要

p1

p2

p3

之中有一個被

rejected

p

的狀态就變成

rejected

,此時第一個被

reject

的執行個體的傳回值,會傳遞給

p

Ok,了解完

Promise.all

我們動手來實作一遍

static all (promises){
        return new myPromise((res,rej)=>{
            let count = 0
            const result = [];
            function addFun(index,resf) {
                result[index]=resf // 這裡用索引别用push,保證傳回的順序
                count++
                if(count==promises.length) {
                    res(result)
                }
            }
            [].forEach.call(promises,(promise,index)=>{
                if(promise instanceof myPromise) {
                    promise.then(success=>{
                        // count ++
                        // result.push(success)
                        addFun(index,success)
                    },err=>{
                        rej(err)
                    })
                }else{
                    addFun(index,promise)
                }
            })
        })
    }
           

Promise.race

Promise.race()

方法同樣是将多個 Promise 執行個體,包裝成一個新的 Promise 執行個體。

const p = Promise.race([p1, p2, p3]);
           

上面代碼中,隻要

p1

p2

p3

之中有一個執行個體率先改變狀态,

p

的狀态就跟着改變。那個率先改變的 Promise 執行個體的傳回值,就傳遞給

p

static race(promises) {
        return new myPromise((res,rej)=>{
            [].forEach.call(promises,promise=>{
                if(promise instanceof myPromise){
                    promise.then(success=>{
                        res(success)
                    },error=>{
                        rej(error)
                    })
                }else{
                    res(promise)
                } 
            })
        })
    }
           

完整代碼

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise狀态
        this.promiseRes = null  // promise傳回值
        this.resolveCallback = [] //成功回調集合
        this.rejectCallback = [] //失敗回調集合
        const resolve = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd //改變狀态
                this.promiseRes = result //傳回值
                //依次調用成功回調
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED //改變狀态
                this.promiseRes = result //傳回值
                // 依次調用失敗回調
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{
            init(resolve,reject)  // 注意this指向
        }catch(err){
            reject(err)
        }
        
    }
    then(onFulfilled,onRejected){
        const {promiseRes,state} = this
        let promise = new myPromise((reso,reje)=>{
            const resolveMyPromise = promiseRes => {
                try{
                    if(typeof onFulfilled !== 'function'){
                        // 如果then的第一個回調不是一個函數,直接忽略,傳回一個新的promise
                        reso(promiseRes)
                    }else{
                        // 擷取第一個回調的執行結果
                        const res = onFulfilled(promiseRes)
                        // 看該執行結果是否是一個promise
                        if(res instanceof myPromise){
                            // 是一個promise,等它狀态改變後再改變then傳回的promise狀态
                            res.then(reso,rej) 
                        }else{
                            // 不是一個promise,将它作為新的promise的resolve
                            reso(res)
                        }
                    }
                }catch(err){
                    //異常,直接将新的promise狀态置為rejected
                    reje(err)
                }
            }
            const rejectMyPromise = promiseRes => {
                try{
                    if(typeof onRejected !== 'function'){
                        // 如果then的第二個回調不是一個函數,直接忽略,傳回一個新的promise
                        reje(promiseRes)
                    }else{
                        // 擷取第二個回調的執行結果
                        const res = onRejected(promiseRes)
                        // 看該執行結果是否是一個promise
                        if(res instanceof myPromise){
                            // 是一個promise,等它狀态改變後再改變then傳回的promise狀态
                            res.then(reso,rej) 
                        }else{
                            // 不是一個promise,将它作為新的promise的resolve
                            reje(res)
                        }
                    }
                    
                }catch(err){
                    //異常,直接将新的promise狀态置為rejected
                    reje(err)
                }
            }
            if(state === myPromise.FULFILLEd) {
                resolveMyPromise(promiseRes)
            }
            if(state === myPromise.REJECTED) {
                rejectMyPromise(promiseRes)
            }
            if(state === myPromise.PENDING){
                if(onFulfilled && typeof onFulfilled === 'function'){
                    this.resolveCallback.push(()=>
                    // 這裡我們用setTimeout來模拟實作then的微任務
                    setTimeout(()=>{
                        resolveMyPromise(this.promiseRes)
                    },0)
                    )
                }
                if(onRejected && typeof onRejected === 'function'){
                    this.rejectCallback.push(()=>
                    // 這裡我們用setTimeout來模拟實作then的微任務
                    setTimeout(()=>{
                        rejectMyPromise(this.promiseRes)
                    },0)
                    )
                }
            }

        })
        return promise
    }
    catch(onRejected) {
        return this.then(undefined,onRejected)
    }
    static all (promises){
        return new myPromise((res,rej)=>{
            let count = 0
            const result = [];
            function addFun(index,resf) {
                result[index]=resf // 這裡用索引别用push,保證傳回的順序
                count++
                if(count==promises.length) {
                    res(result)
                }
            }
            [].forEach.call(promises,(promise,index)=>{
                if(promise instanceof myPromise) {
                    promise.then(success=>{
                        addFun(index,success)
                    },err=>{
                        rej(err)
                    })
                }else{
                    addFun(index,promise)
                }
            })
        })
    }
    static race(promises) {
        return new myPromise((res,rej)=>{
            [].forEach.call(promises,promise=>{
                if(promise instanceof myPromise){
                    promise.then(success=>{
                        res(success)
                    },error=>{
                        rej(error)
                    })
                }else{
                    res(promise)
                } 
            })
        })
    }
    static resolve(v){
        //1.參數是一個Promise執行個體,直接傳回
        if(v instanceof myPromise){
            return v
        }
        //2.參數是一個thenable對象,轉為Promise後執行該對象的then方法
        if(typeof v === 'object' && typeof v.then === 'function'){
            return new myPromise((res,rej)=>{
                v.then(res,rej)
            })
        }
        //3.沒有參數,直接傳回一個resolved狀态的promise
        if(!v){
            return new myPromise(res=>{
                res()
            })
        }
        //4.參數是一個原始值,傳回一個新的Promise,狀态為resolved
        return new myPromise(res=>{
            res(v)
        })
    }
    static reject(v){
        return new myPromise((res,rej)=>{
            rej(v)
        })
    }
}
           

總結

OK,上面跟大家一起過了一遍

Promise

的用法以及自己動手實作了一遍

Promise

,想必看完這篇文章,大家對

Promise

會有一個更加清晰的認識。

我是

南玖

,感謝各位的:「點贊和關注」,我們下期見!

作者:前端南玖

出處:https://www.cnblogs.com/songyao666/

-------------------------------------------

個性簽名:智者創造機會,強者把握機會,弱者坐等機會。做一個靈魂有趣的人!

如果覺得這篇文章對你有小小的幫助的話,可以關注下方公衆号,在該公衆号同樣會推送技術文章給大家,謝謝~

歡迎加入前端技術交流群:928029210

從如何使用到如何實作一個Promise

繼續閱讀