重學(xué)promise之手寫(xiě)源碼

一、什么是Promise?

Promise是js異步編程的解決方案。從語(yǔ)法上看,Promise是一個(gè)構(gòu)造函數(shù);從功能上看,promise對(duì)象用來(lái)封裝一個(gè)異步操作并可以獲取其結(jié)果;

每個(gè)Promise實(shí)例對(duì)象都有三個(gè)狀態(tài),分別為pending(初始,狀態(tài)未改變)、fulfilled(成功)、rejected(失?。?

promise對(duì)象的狀態(tài)只能從pending到fulfilled或從pending到rejected變化,并且狀態(tài)改變后不再改變;

二、為什么使用Promise?

Promise指定回調(diào)函數(shù)的方式或時(shí)機(jī)更靈活

在傳統(tǒng)回調(diào)函數(shù)中,必須在異步任務(wù)啟動(dòng)前指定回調(diào)函數(shù);
而在使用Promise時(shí),我們通常先啟動(dòng)異步任務(wù),然后返回一個(gè)promise對(duì)象,最后通過(guò)then/catch指定回調(diào)函數(shù)(甚至可以在異步任務(wù)結(jié)束后指定回調(diào)函數(shù));

/* 傳統(tǒng)回調(diào)函數(shù) */
  $.ajax({
    url: '/user/info',
    method: 'post',
    success: function() {
      // 請(qǐng)求成功回調(diào),需要在在開(kāi)啟請(qǐng)求之前指定
    }
  })
  /* promise指定回調(diào) */
  // 1. 開(kāi)啟異步任務(wù),返回一個(gè)promise對(duì)象
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, 200);
  })
  // 2. 指定回調(diào)函數(shù)
  promise.then(
    value => {console.log('resolve', value)},
    reason => {console.log('reject', reason)}
  )
  // 3.異步任務(wù)結(jié)束后指定回調(diào)函數(shù)
  setTimeout(() => {
    promise.then(
      value => {console.log('resolve', value)},
      reason => {console.log('reject', reason)}
    )
  }, 2000)
Promise支持鏈?zhǔn)秸{(diào)用,可以解決回調(diào)地獄問(wèn)題

什么是回調(diào)地獄? 回調(diào)函數(shù)嵌套調(diào)用,上一個(gè)回調(diào)函數(shù)返回的結(jié)果是嵌套回調(diào)函數(shù)執(zhí)行的條件;
回調(diào)地獄的問(wèn)題? 不便于開(kāi)發(fā)者閱讀,不便于異常排查;
解決方案?Promise鏈?zhǔn)秸{(diào)用;
js異步任務(wù)的終極解決方案? async/await;

三、理解Promise的一些關(guān)鍵性問(wèn)題

如何改變promies的狀態(tài)?

上面已經(jīng)提到了promise的狀態(tài)有三種,pending、fulfilled、rejected;
一般說(shuō)到改變狀態(tài)我們都會(huì)想到resolve()和reject()這兩個(gè)方法,其實(shí)還有一種改變狀態(tài)的方式,這里總結(jié)一下:

  • 使用resolve() 狀態(tài)由pending變?yōu)閒ulfilled;
  • 使用reject() 狀態(tài)由pending變?yōu)閞ejected;
  • throw Error('error')/ throw 任意值 狀態(tài)由pending;
一個(gè)promise實(shí)例指定多個(gè)成功/失敗的回調(diào),都會(huì)被調(diào)用嗎?

當(dāng)promise狀態(tài)改變時(shí),對(duì)應(yīng)的回調(diào)函數(shù)都會(huì)被調(diào)用;

const promise = new Promise((resolve, reject) => {
  resolve(1) // 觸發(fā)所有成功狀態(tài)的回調(diào)函數(shù)
})
// 指定第一個(gè)回調(diào)
promise.then(
  value => {console.log(value)}, // 輸出1
  reason => {console.log(reason)}
)
// 指定第二個(gè)回調(diào)
promise.then(
  value => {console.log(value)}, // 輸出1
  reason => { console.log(reason) }
)
改變promise狀態(tài)和指定回調(diào)函數(shù)誰(shuí)先誰(shuí)后?

都有可能,正常情況下先指定回調(diào)函數(shù)再改變狀態(tài),但也可以先改變狀態(tài)再指定回調(diào)函數(shù);
如何先改變狀態(tài)再指定回調(diào)函數(shù)?

  • 在執(zhí)行器中同步調(diào)用resolve()/reject();
  • 延遲更長(zhǎng)的時(shí)間調(diào)用then,例如使用定時(shí)器;
    什么時(shí)候才能得到數(shù)據(jù)?當(dāng)調(diào)用回調(diào)函數(shù)時(shí),都可以在回調(diào)函數(shù)中得到數(shù)據(jù);
promise.then()返回的新promise對(duì)象的狀態(tài)由誰(shuí)決定?

總結(jié):由then指定的回調(diào)函數(shù)(成功/失敗)的結(jié)果決定;
具體:

  • 如果拋出異常,新promise對(duì)象的狀態(tài)變?yōu)閞ejected,結(jié)果是拋出的異常
  • 如果返回的是非promise的任意值,新promise對(duì)象的狀態(tài)變?yōu)閒ulfilled,結(jié)果是當(dāng)前任意值;
  • 如果返回另一個(gè)promise對(duì)象,此promise對(duì)象的狀態(tài)及結(jié)果就會(huì)成為新promise的狀態(tài)和結(jié)果;
new Promise((resolve, reject) => {
  resolve(1) // 輸出順序:1 => 3 => 5
  // reject(1) // 輸出順序:2 => 4 => 5
}).then(
  value => {
    console.log('onResolve1', value) // 順序1
    return Promise.resolve(value + 2) // 返回一個(gè)新的promise,且狀態(tài)為fulfilled
  },
  reason => {
    console.log('onReject1', reason) // 順序2
    throw 3 // 拋出異常
  }
).then(
  value => { console.log('onResolve2', value) // 順序3 },
  reason => { console.log('onReject2', reason) // 順序4 }
).then(
  value => { console.log('onResolve3', value) // 順序5 },
  reason => { console.log('onReject3', reason) // 順序6 }
)
Promise怎么串聯(lián)多個(gè)操作任務(wù)?

promise.then()返回一個(gè)新的promise對(duì)象,通過(guò)then的鏈?zhǔn)秸{(diào)用,可以串聯(lián)多個(gè)同步/異步任務(wù);
其中串聯(lián)異步任務(wù)需要new一個(gè)promise對(duì)象;

new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('執(zhí)行任務(wù)1(異步)')
    resolve(1)
  }, 1000);
}).then(value => {
  console.log('任務(wù)1的結(jié)果:', value)
  console.log('執(zhí)行任務(wù)2(同步)')
  return 2
}).then(value => {
  console.log('任務(wù)2的結(jié)果: ', value)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('執(zhí)行任務(wù)3(異步)')
      resolve(3)
    }, 2000);
  })
}).then(value => {
  console.log('任務(wù)3的結(jié)果', value)
})
Promise的異常傳透怎么實(shí)現(xiàn)的?

當(dāng)我們使用then的鏈?zhǔn)秸{(diào)用時(shí),通常會(huì)在調(diào)用鏈的最后指定失敗的回調(diào)catch;
當(dāng)catch前面的then中有一環(huán)出現(xiàn)了異?;蛘郀顟B(tài)變?yōu)閞ejected,都會(huì)通過(guò)then依次傳遞到catch中,注意并不是直接跳到catch;
因?yàn)槲覀兘y(tǒng)一用catch做異常處理了,所以一般我們不在then中寫(xiě)rejected回調(diào),這時(shí)rejected的回調(diào)默認(rèn)為 reason => { throw reason };

new Promise((resolve, reject) => {
  // resolve(1)
  reject(1)
}).then(
  value => {
    console.log('onResolve1', value)
    return 2
  },
  reason => { throw reason } // 失敗回調(diào)的默認(rèn)值, 將異常傳透給catch
).then(
  value => { console.log('onResolve2', value) }
).catch(
  reason => { console.log('onReject', reason) }
)
怎么中斷promise鏈?

在回調(diào)函數(shù)中返回一個(gè)狀態(tài)為pending的promise,可以中斷promise鏈;

new Promise((resolve, reject) => {
  // resolve(1)
  reject(1)
}).then(
  value => { console.log('onResolve1', value) }
).catch(reason => {
  console.log('onReject', reason)
  return new Promise(() => {}) // 返回一個(gè)pending狀態(tài)的promise,中止promise鏈
}).then(
  value => { console.log('onResolve3', value) }
)

四、源碼實(shí)現(xiàn)

(function() {

  const PENDING = 'pending'
  const FULFILLED = 'fulfilled'
  const REJECTED = 'rejected'

  /* 
    Promise類(lèi)
    excutor:執(zhí)行器函數(shù)(同步)
  */
  class Promise {
    constructor(excutor) {
      const _this = this
      this.status = PENDING // promise實(shí)例對(duì)象的狀態(tài),初始值為pending
      this.data = undefined // 存儲(chǔ)結(jié)果數(shù)據(jù)的屬性
      this.callbacks = [] // 回調(diào)函數(shù),每個(gè)元素的數(shù)據(jù)解構(gòu): { onResolved() {}, onRejected() {} }

      // 觸發(fā)成功回調(diào)
      function resolve(value) {
        // 當(dāng)前狀態(tài)不是pending,結(jié)束
        if (_this.status !== PENDING) return false
        // 修改當(dāng)前狀態(tài)為fulfilled
        _this.status = FULFILLED
        // 保存數(shù)據(jù)結(jié)果
        _this.data = value
        // 如果由待執(zhí)行的回調(diào)函數(shù),立即異步執(zhí)行回調(diào)函數(shù)的onResolved
        if (_this.callbacks.length) {
          setTimeout(() => {
            _this.callbacks.forEach(callbackObj => {
              callbackObj.onResolved(value)
            })
          })
        }
      }

      // 觸發(fā)失敗回調(diào)
      function reject(reason) {
        // 當(dāng)前狀態(tài)不是pending,結(jié)束
        if (_this.status !== PENDING) return false
        // 修改當(dāng)前狀態(tài)為rejected
        _this.status = REJECTED
        // 保存數(shù)據(jù)結(jié)果
        _this.data = reason
        // 如果有待執(zhí)行的回調(diào)函數(shù),立即異步執(zhí)行回調(diào)函數(shù)的onRejected
        if (_this.callbacks.length) {
          setTimeout(() => {
            _this.callbacks.forEach(callbackObj => {
              callbackObj.onRejected(reason)
            })
          })
        }
      }

      // 處理執(zhí)行器異常
      try {
        excutor(resolve, reject)
      } catch (error) {
        reject(error)
      }
    }
    /* 
      then方法
      指定成功和失敗的回調(diào)
      返回一個(gè)promise
    */
    then(onResolved, onRejected) {
      const _this = this

      onResolved = typeof onResolved === 'function' ? onResolved : value => value // 向下傳遞value
      // 指定默認(rèn)的失敗回調(diào)onRejected(實(shí)現(xiàn)異常傳透的關(guān)鍵)
      onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

      return new Promise((resolve, reject) => {
        // 調(diào)用指定回調(diào)函數(shù)處理,根據(jù)執(zhí)行結(jié)果,改變r(jià)eturn的promise的狀態(tài)
        function handle(callback) {
          /* 
            1. 如果拋出異常,return的Promise的結(jié)果就是失敗的,reason就是error
            2. 如果返回的結(jié)果不是promise類(lèi)型,return的Promise的結(jié)果就是成功的,value就是該返回的結(jié)果
            3. 如果是promise,return的Promise的結(jié)果就是該promise的結(jié)果
          */
          try {
            const result = callback(_this.data)
            if (result instanceof Promise) {
              // 3. 返回的結(jié)果是promise,return的Promise的結(jié)果就是該promise的結(jié)果
              result.then(
                value => resolve(value),
                reason => reject(reason)
              )
              // result.then(resolve, reject)
            } else {
              // 2. 返回的結(jié)果不是promise類(lèi)型,return的Promise的結(jié)果就是成功的,value就是該返回的結(jié)果
              resolve(result)
            }
          } catch (error) {
            // 1. 拋出異常,return的Promise的結(jié)果就是失敗的,reason就是error
            reject(error)
          }
        }
        // 狀態(tài)還沒(méi)改變,先將回調(diào)函數(shù)存儲(chǔ)起來(lái),用于狀態(tài)改變時(shí)調(diào)用
        if (this.status === PENDING) {
          this.callbacks.push({
            onResolved () {
              handle(onResolved)
            },
            onRejected () {
              handle(onRejected)
            }
          })
        } else if (this.status === FULFILLED) {
          // 狀態(tài)是fulfilled,異步執(zhí)行成功回調(diào)
          setTimeout(() => {
            handle(onResolved)
          })
        } else {
          // 狀態(tài)是rejected,異步執(zhí)行失敗回調(diào)
          setTimeout(() => {
            handle(onRejected)
          })
        }
      })
    }

    /* 
      catch方法
      指定失敗的回調(diào)
      返回一個(gè)promise
    */
    catch(onRejected) {
      return this.then(undefined, onRejected)
    }

    /* 
      Promise的靜態(tài)方法resolve
      返回一個(gè)指定value的成功/失敗的promise
    */
    static resolve = function(value) {
      return new Promise((resolve, reject) => {
        // value是一個(gè)promise
        if (value instanceof Promise) {
          value.then(resolve, reject)
        } else { // value不是一個(gè)promise
          resolve(value)
        }
      })
    }

    /* 
      Promise的靜態(tài)方法reject
      返回一個(gè)指定reason的失敗的promise
    */
    static reject = function(reason) {
      return new Promise((resolve, reject) => {
        reject(reason)
      })
    }

    /* 
      Promise的靜態(tài)方法all
      返回一個(gè)promise,當(dāng)多有promise都成功時(shí)才成功,有一個(gè)失敗則失敗
    */
    static all = function(promises) {
      // 定義一個(gè)指定長(zhǎng)度的數(shù)組,用于存儲(chǔ)resolve的值
      const values = new Array(promises.length)
      // 計(jì)數(shù),用于記錄成功的promise次數(shù)
      let resolveCount = 0
      return new Promise((resolve, reject) => {
        promises.forEach((p, index) => {
          Promise.resolve(p).then(
            value => {
              values[index] = value
              resolveCount ++
              // 如果全部都成功了,將返回的promise改為成功
              if (resolveCount === promises.length) {
                resolve(values)
              }
            },
            // 有一個(gè)promise失敗,將返回的promise改為失敗
            reason => {
              reject(reason)
            }
          )
        })
      })
    }

    /* 
      Promise的靜態(tài)方法race
      返回一個(gè)promise,其結(jié)果由第一 個(gè)完成的promise決定
    */
    static race = function(promises) {
      return new Promise((resolve, reject) => {
        promises.forEach(p => {
          Promise.resolve(p).then(
            // 有一個(gè)promise成功,將返回的promise改為成功
            value => {
              resolve(value)
            },
            // 有一個(gè)promise失敗,將返回的promise改為失敗
            reason => {
              reject(reason)
            }
          )
        })
      })
    }
  }
  // 暴露Promise
  window.Promise = 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ù)。

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

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