深入理解Promise

ES6提供的Promise對(duì)象是異步控制相較于回調(diào)的更好的一種方法。包括ES8提供的asyncFunction本質(zhì)上也是基于Promise和生成器的結(jié)合,因此在已經(jīng)了解Promise對(duì)象的常用API基礎(chǔ)上,更加深入的去了解如何使用Promise去解決一些常見(jiàn)的難題對(duì)于開(kāi)發(fā)將會(huì)有一些幫助。

文章閱讀前,希望讀者已經(jīng)對(duì)Promise的使用及相關(guān)api有了一定的了解 。[ 快速學(xué)習(xí)Promise ]

一、如何對(duì)Promise異步任務(wù)進(jìn)行超時(shí)監(jiān)聽(tīng)

任務(wù)描述

作為一道開(kāi)胃小菜,這個(gè)問(wèn)題還是不難解決的。平時(shí)對(duì)于一些異步任務(wù)很有可能需要進(jìn)行超時(shí)監(jiān)聽(tīng),那么如何利用Promise來(lái)進(jìn)行超時(shí)監(jiān)聽(tīng)/監(jiān)控呢?

解決方案

// 封裝一個(gè)嚴(yán)格任務(wù)函數(shù),第一個(gè)參數(shù)為promise對(duì)象,第二個(gè)參數(shù)為判定的超時(shí)標(biāo)準(zhǔn),默認(rèn)3s
function strictTack(promise,delay = 3){
  // 函數(shù)返回一個(gè)超時(shí)基準(zhǔn)promise對(duì)象
  let promiseTimeout = function(delay){
    return new Promise((res,rej)=>{
      setTimeout(()=>{
        rej(new Error("運(yùn)行超時(shí)!"))
      },1000 * delay)
    })
  }
  // race,參數(shù)數(shù)組內(nèi)的promise并發(fā)執(zhí)行,一旦其中有一個(gè)promise對(duì)象產(chǎn)生判決就會(huì)終止其余的promise對(duì)象
  return Promise.race([promise,promiseTimeout(delay)])
}
// 異步任務(wù)p1
let p1 = new Promise((res,rej)=>{
  setTimeout(()=>{
    res("p1 was resoved")
  },1000 * 2)
})

// 異步任務(wù)p2
let p2 = new Promise((res,rej)=>{
  setTimeout(()=>{
    res("p2 was resoved")
  },1000 * 4)
})

strictTack(p1)
.then(e=>{
  console.log(e)// p1 was resoved
}).catch(err=>{
  console.log(err)
}) 

strictTack(p2)
.then(e=>{
  console.log(e)
}).catch(err=>{
  console.log(err) // Error:運(yùn)行超時(shí)!
}) 

任務(wù)總結(jié)

對(duì)于這個(gè)問(wèn)題來(lái)說(shuō),核心部分就是利用好Promise.race這個(gè)API,因?yàn)槭褂盟鶊?zhí)行的的Promise對(duì)象中的異步任務(wù)都是“競(jìng)態(tài)”的,只接受第一個(gè)發(fā)生判決的Promise對(duì)象。那么此時(shí)采用超時(shí)基準(zhǔn)promise對(duì)象配合race正好巧妙的解決了超時(shí)監(jiān)聽(tīng)的問(wèn)題。

二、如何自己實(shí)現(xiàn)一個(gè)Promise

任務(wù)描述

這種具有一定難bian度tai的題目一般會(huì)在面試中遇到,那么如何手動(dòng)實(shí)現(xiàn)一個(gè)Promise(或部分功能)?

解決方案

  1. V 0.1初步版本的Promise (不支持正規(guī)Promise的鏈?zhǔn)秸{(diào)用)

    class PromisePolyfill{
      constructor(exector = ()=>{}){
        this.status = 'pending' // promise當(dāng)前狀態(tài)
        this.reason = undefined // 用戶回顯到reject函數(shù)的值
        this.value = undefined // 用戶回顯到resolve函數(shù)的值
        // 成功事件回調(diào)隊(duì)列
        this.onFulfilledCallBacks = []
        // 失敗事件回調(diào)隊(duì)列
        this.onRejectedCallBacks = []
        
        //實(shí)現(xiàn)resolve函數(shù)
        let resolve = (value)=>{
          if(this.status === 'pending'){
            this.value = value
            this.status = 'fulfilled'
            this.onFulfilledCallBacks.map(e=>e())
          }
        }
    
        // 實(shí)現(xiàn)reject函數(shù)
        let reject = (reason)=>{
          if(this.status === 'pending'){
            this.reason = reason
            this.status = 'rejected'
            this.onRejectedCallBacks.map(e=>e())
          }
        }
    
        try{
          // 運(yùn)行執(zhí)行器
          exector(resolve,reject)
        }catch(err){
          reject(err)
        }
      }
    
      then(onFulfilled,onRejected){
        // 同步情況
        if(this.status === 'fulfilled' && onFulfilled){
          onFulfilled(this.value)
        }
        if(this.status === 'rejected' && onRejected){
          onRejected(this.reason)
        }
        // 異步情況
        if(this.status === 'pending'){
          // 將任務(wù)成功回調(diào)加入到成功隊(duì)列中
          onFulfilled && this.onFulfilledCallBacks.push(()=>{
            onFulfilled(this.value)
          })
           // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中
          onRejected && this.onRejectedCallBacks.push(()=>{
            onRejected(this.reason)
          })
        }
        return this
      }
        
      catch(onRejected){
            // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中
        onRejected && this.onRejectedCallBacks.push(()=>{
          onRejected(this.reason)
        })
        return this
      }
    }
    
  2. V 0.2支持鏈?zhǔn)秸{(diào)用,但是未實(shí)現(xiàn)Promise的相關(guān)API -- all、race、reject、resolve

    class PromisePolyfill{
      constructor(exector = ()=>{}){
        this.status = 'pending' // promise當(dāng)前狀態(tài)
        this.reason = undefined // 用戶回顯到reject函數(shù)的值
        this.value = undefined // 用戶回顯到resolve函數(shù)的值
        // 成功事件回調(diào)隊(duì)列
        this.onFulfilledCallBacks = []
        // 失敗事件回調(diào)隊(duì)列
        this.onRejectedCallBacks = []
        
        //實(shí)現(xiàn)resolve函數(shù)
        let resolve = (value)=>{
          if(this.status === 'pending'){
            this.value = value
            this.status = 'fulfilled'
            this.onFulfilledCallBacks.map(e=>e())
          }
        }
    
        // 實(shí)現(xiàn)reject函數(shù)
        let reject = (reason)=>{
          if(this.status === 'pending'){
            this.reason = reason
            this.status = 'rejected'
            this.onRejectedCallBacks.map(e=>e())
          }
        }
    
        try{
          // 運(yùn)行執(zhí)行器
          exector(resolve,reject)
        }catch(err){
          reject(err)
        }
      }
    
      then(onFulfilled,onRejected){
    
        let promise2 = new PromisePolyfill((resolve, reject)=>{
          if(this.status === 'fulfilled' && onFulfilled){
            let x = onFulfilled(this.value)
            // resolvePromise函數(shù),處理自己return的promise和默認(rèn)的promise2的關(guān)系
            resolvePromise(promise2, x, resolve, reject)
          }
          if(this.status === 'rejected' && onRejected){
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          }
          // 異步情況
          if(this.status === 'pending'){
            // 將任務(wù)成功回調(diào)加入到成功隊(duì)列中
            onFulfilled && this.onFulfilledCallBacks.push(()=>{
              let x = onFulfilled(this.value)
              // resolvePromise函數(shù),處理自己return的promise和默認(rèn)的promise2的關(guān)系
              resolvePromise(promise2, x, resolve, reject)
            })
             // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中
            onRejected && this.onRejectedCallBacks.push(()=>{
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            })
          }
        })
        return promise2
      }
    }
    function resolvePromise(promise2, x, resolve, reject){
      // 循環(huán)引用報(bào)錯(cuò)
      if(x === promise2){
        // reject報(bào)錯(cuò)
        return reject(new TypeError('Chaining cycle detected for promise'));
      }
      // 防止多次調(diào)用
      let called;
      // x不是null 且x是對(duì)象或者函數(shù)
      if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
          // A+規(guī)定,聲明then = x的then方法
          let then = x.then;
          // 如果then是函數(shù),就默認(rèn)是promise了
          if (typeof then === 'function') { 
            // 就讓then執(zhí)行 第一個(gè)參數(shù)是this   后面是成功的回調(diào) 和 失敗的回調(diào)
            then.call(x, y => {
              // 成功和失敗只能調(diào)用一個(gè)
              if (called) return;
              called = true;
              // resolve的結(jié)果依舊是promise 那就繼續(xù)解析
              resolvePromise(promise2, y, resolve, reject);
            }, err => {
              // 成功和失敗只能調(diào)用一個(gè)
              if (called) return;
              called = true;
              reject(err);// 失敗了就失敗了
            })
          } else {
            resolve(x); // 直接成功即可
          }
        } catch (e) {
          // 也屬于失敗
          if (called) return;
          called = true;
          // 取then出錯(cuò)了那就不要在繼續(xù)執(zhí)行了
          reject(e); 
        }
      } else {
        resolve(x);
      }
    }
    
  3. v0.2基礎(chǔ)上封裝相關(guān)API

    • all(iterable) 這個(gè)方法接受一個(gè)promise數(shù)組,返回一個(gè)新的promise對(duì)象,該promise對(duì)象在iterable參數(shù)對(duì)象里所有的promise對(duì)象都成功的時(shí)候才會(huì)觸發(fā)成功,一旦有任何一個(gè)iterable里面的promise對(duì)象失敗則立即觸發(fā)該promise對(duì)象的失敗。
    PromisePolyfill.all = function (promises){
      function processData (){
        let Arr = []
        let i = 0
        return function (index,data,resolve){
          Arr[index] = data
          i++
          if(i === promises.length){
            resolve(Arr)
          }
        }
      }
      let process = processData()
      return new PromisePolyfill((resolve,reject)=>{
        promises.map((promise,index) => {
          promise.then(data=>{
            process(index,data,resolve)
          },reject)
        })
      })
    }
    
    • race(iterable) 這個(gè)方法接受一個(gè)promise數(shù)組。當(dāng)iterable參數(shù)里的任意一個(gè)子promise被成功或失敗后,父promise馬上也會(huì)用子promise的成功返回值或失敗詳情作為參數(shù)調(diào)用父promise綁定的相應(yīng)句柄,并返回該promise對(duì)象。
    PromisePolyfill.race = function(promises){
      return new PromisePolyfill((resolve,reject)=>{
        promises.map(promise=>{
          promise.then(resolve,reject)
        })
      })
    }
    
    • resolve(value) 返回一個(gè)狀態(tài)由給定value決定的Promise對(duì)象。如果該值是thenable(即,帶有then方法的對(duì)象),返回的Promise對(duì)象的最終狀態(tài)由then方法執(zhí)行決定;否則的話(該value為空,基本類型或者不帶then方法的對(duì)象),返回的Promise對(duì)象狀態(tài)為fulfilled,并且將該value傳遞給對(duì)應(yīng)的then方法。
    PromisePolyfill.resolve = function(data){
      return new PromisePolyfill((resolve,reject)=>{
        resolve(data)
      })
    }
    
    • reject(value) 返回一個(gè)狀態(tài)為失敗的Promise對(duì)象,并將給定的失敗信息傳遞給對(duì)應(yīng)的處理方法
    PromisePolyfill.reject = function(data){
      return new PromisePolyfill((resolve,reject)=>{
         reject(data)
      })
    }
    

三、手寫(xiě)一個(gè)Promise的Ajax函數(shù)

任務(wù)描述

老生常談的問(wèn)題了,面試官喜歡問(wèn)的問(wèn)題。主要思想實(shí)現(xiàn)出來(lái)就好了

解決方案

function ajax (params){
    return new Promise((resolve,reject)=>{
        let request = new XMLHttpRequset()
        //設(shè)置請(qǐng)求方式和請(qǐng)求地址
        request.open(params.type || 'get',params.url)
        
        request.onreadystatechange = ()=>{
            //當(dāng)請(qǐng)求返回響應(yīng)時(shí)
            if(request.readyState === 4){
                // 默認(rèn)情況下返回200意味著請(qǐng)求成功
                if(request.status === 200){
                    resolve(JSON.parse(request.responseText))
                }else{
                    reject(JSON.parse(request.responseText))
                }
            }
        }
        //攜帶請(qǐng)求頭發(fā)送請(qǐng)求
        request.send(params.data || null)
    })
}

四、理論部分

關(guān)于then和catch的返回值

如果自己去實(shí)現(xiàn)了一次Promise對(duì)象之后會(huì)發(fā)現(xiàn),thencatch如果沒(méi)有返回一個(gè)標(biāo)準(zhǔn)的promise對(duì)象時(shí),將會(huì)默認(rèn)返回一個(gè)Promise.resolve(基本值),即使你沒(méi)有寫(xiě)任何return語(yǔ)句。

let p1 = new Promise((res,rej)=>{
  setTimeout(()=>{
    // rej("p1 was rejected")
    res("p1 was resoved")
  },1000 * 2)
})

p1.then(res=>{
  console.log("Then1:"+res)
}).then(res=>{
  console.log("Then2:"+res)
  return 'Hello'
}).catch(err=>{
  console.log("Error1:"+err)
  return Promise.reject("Error For Catch 1")
}).then(res=>{
  console.log("Then3:"+res)
  return Promise.reject('ERROR For Then 3')
}).then(res=>{
  console.log("Then4:"+res)
}).catch(err=>{
  console.log("Error2:"+err)
}).then(res=>{
  console.log("Then5:"+res)
})

/*
結(jié)果
Then1:p1 was rejected
Then2:undefined
Then3:Hello
Error2:ERROR For Then 3
Then5:undefined
*/

經(jīng)過(guò)分析不難發(fā)現(xiàn),對(duì)于一個(gè)Promise中的鏈?zhǔn)秸{(diào)用的順序是這樣的:

  1. 對(duì)于一個(gè)then/catch,默認(rèn)返回為一個(gè)resolve
  2. 當(dāng)返回一個(gè)reject時(shí),將調(diào)用當(dāng)前節(jié)點(diǎn)往后的最近的catch節(jié)點(diǎn)中的回調(diào)(跳過(guò)中間的then
  3. 如果當(dāng)前節(jié)點(diǎn)并沒(méi)有返回reject 但是恰巧后面的節(jié)點(diǎn)就是是catch,那么將會(huì)跳過(guò)這個(gè)(或連續(xù)幾個(gè))catch,直接到后面最近的then節(jié)點(diǎn)

以上為現(xiàn)象,原理請(qǐng)參考第二節(jié)Promise的實(shí)現(xiàn)

關(guān)于async Function (async/await)

asyncFunction是ES8中提出的更好的異步解決方案,實(shí)際用起來(lái)也確實(shí)如此。那么asyncFunctionPromise之間存在什么樣的關(guān)系呢?

[ 快速學(xué)習(xí)async Function ]

async function fetch(){
  console.log("Fetch is RUNING") // 立即輸出Fetch is RUNING
  let  data1 = await new Promise((res,rej)=>{
    setTimeout(()=>{
      res(1)
    },1000 * 2)
  })
  console.log(data1) //時(shí)間線至少2s后打印1
  let  data2 = await new Promise((res,rej)=>{
    setTimeout(()=>{
      res(2)
    },1000 * 5)
  })
  console.log(data2) // 時(shí)間線至少2+5=7s后打印2
  console.log(data1+data2) // 緊接著上一句執(zhí)行,打印 3
}
fetch()

上例中展示了async/await的基本用法和執(zhí)行過(guò)程

  1. 第一句同步打印任務(wù)可以直接輸出。
  2. 第二句遇到await,將會(huì)等待當(dāng)前await任務(wù)返回Promise.resolve后才會(huì)繼續(xù)執(zhí)行下一句。這種模式和生成器的模式非常相像,async/await優(yōu)于生成器的地方就是,生成器需要手動(dòng)next才能進(jìn)行下一步,而async/await是自動(dòng)next,顯然實(shí)現(xiàn)上要更為復(fù)雜。
  3. 依次類推...

從這個(gè)例子中可以看出,多個(gè)await產(chǎn)生的異步任務(wù)是逐個(gè)執(zhí)行的,而不是并發(fā)。所以想要實(shí)現(xiàn)多異步任務(wù)并發(fā)控制,仍然需要使用Promise.all或者Promise.race。

接下來(lái),再來(lái)看個(gè)例子

async function fetch(){
  let  data1 = await new Promise((res,rej)=>{
    setTimeout(()=>{
      res(1)
    },1000 * 2)
  })
  let  data2 = await new Promise((res,rej)=>{
    setTimeout(()=>{
      res(2)
    },1000 * 5)
  })
  return data1 + data2
}

fetch().then(res=>{
  console.log(res) // 至少7s后輸出 3
})

你沒(méi)看錯(cuò),asyncFunction將會(huì)默認(rèn)返回一個(gè)Promise對(duì)象,和Promise.prototype.then返回Promise的效果一致

到這兒不難看出Promise的重要性了吧,現(xiàn)在的主要異步任務(wù)控制方式實(shí)際上都沒(méi)有離開(kāi)Promise

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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