從實(shí)現(xiàn)一個(gè)Promise說(shuō)起

前言

const p = new Promise((resolve, reject) => {
  console.log('A')
  setTimeout(() => {
    console.log('B')
    resolve('C')
  })
})

p.then(res => {
  console.log(res)
})

// A B C D

盡管工作中用了無(wú)數(shù)次Promise async await,但是在寫(xiě)下這篇文章之前,卻不知道Promise背后發(fā)生了些什么,我一直以為的邏輯是先等待Promise構(gòu)造方法中的異步函數(shù)完成后,再調(diào)用then方法執(zhí)行其中的函數(shù)。然而事情并沒(méi)有這么簡(jiǎn)單,這篇文章將以深入淺出的方式理解Promise背后究竟發(fā)生了什么

構(gòu)造一個(gè)Promise

按照Promise/A+規(guī)范,一個(gè)Promise應(yīng)該包含以下數(shù)據(jù)結(jié)構(gòu)

interface IPromise {
  status: STATUS // 表明當(dāng)前Promise的狀態(tài),不可逆,在進(jìn)行then添加方法時(shí),會(huì)根據(jù)這個(gè)狀態(tài)做出不同的處理
  value: any // 異步函數(shù)執(zhí)行成功后返回的值
  reason: any // 異步函數(shù)執(zhí)行失敗后返回的值
  onResolvedCallbacks: Function[] // 保存then方法添加的成功后執(zhí)行函數(shù)
  onRejectCallbacks: Function[] // 保存then方法添加的失敗后的執(zhí)行函數(shù)
}

enum STATUS {
  PENDING,
  FULFILLED,
  REJECTED
}

接著動(dòng)手實(shí)現(xiàn)一個(gè)Promise,因?yàn)樵赥S環(huán)境中Promise已經(jīng)有了,為了避免和已有的沖突,我把自己構(gòu)造的對(duì)象命名為MyPromise

class MyPromise {
  private status: STATUS
  private value: any
  private reason: any
  private onResolvedCallBacks: Function [] = []
  private onRejectCallBacks: Function [] = []

  constructor(executor: Function) {
    const self = this
    function resolve(value: any) {
      // 改變當(dāng)前Promise的狀態(tài)
      if (p.status === STATUS.PENDING) {
        p.stats = STATUS.FULFILLED
        p.value = value
        p.onResolveCallbacks.forEach(fun => {
          fun(p.value)
        })
      }
    }
    function reject(reason: any) {
      if (p.status === STATUS.PENDING) {
        p.status = STATUS.REJECTED
        p.reason = reason
        p.onRejectCallbacks.forEach(fun => fun(reason))
      }
    }
  }

  public then(onFulfilled: Function, onReject?: Function) {
    this.onResolveCallbacks.push(onFulfilled)
    if (onReject) {
      this.onRjectCallback.push(onRject)
      return
    }
    return this
  }
}

這是一個(gè)最最基本的Promise實(shí)現(xiàn),寫(xiě)到這里我們?cè)囍鴱拇a中了解下Promise究竟干了些什么。
我們知道JS是異步非阻塞單線程的語(yǔ)言,遇到異步任務(wù)時(shí),將會(huì)向事件隊(duì)列添加一個(gè)函數(shù),直到異步任務(wù)完成時(shí),線程再執(zhí)行這個(gè)函數(shù),基于此,在JS中很多地方用到了訂閱者模式。

Promise正好是一個(gè)訂閱者模式的實(shí)現(xiàn)executor就是我們添加的訂閱的數(shù)據(jù)源,我們向這個(gè)源注冊(cè)了兩個(gè)鉤子resolve, reject,分別在異步事件的成功和失敗時(shí)執(zhí)行,相當(dāng)于訂閱者的notify方法。

then方法則是向訂閱者注冊(cè)事件。這樣就能初步理解Promise干了什么。

resolve,reject方法的改進(jìn)

按照Promise預(yù)期的設(shè)計(jì),then方法時(shí)同步的向Promise的待處理隊(duì)列添加函數(shù),而executor函數(shù)則是異步的執(zhí)行一個(gè)函數(shù),再調(diào)用其中的resolve或者reject方法,也就是說(shuō)then一定先于executor執(zhí)行。上面的代碼中如果executor是一個(gè)同步的方法,那么新建這個(gè)MyPromise實(shí)例時(shí),resolve就已經(jīng)被調(diào)用了,導(dǎo)致then添加的方法無(wú)法執(zhí)行。所以我們需要做出一定的處理,保證resolve之前,已經(jīng)注冊(cè)了事件處理函數(shù)

function reject (value: any) {
  // 調(diào)用setImmediate方法,保證resolve一定會(huì)在通過(guò)then同步的注冊(cè)的方法后調(diào)用
  // setImmediate將會(huì)把回調(diào)中的函數(shù)加入到下一個(gè)task,優(yōu)先級(jí)要比setTimeout高
  // JS中的Promise.resolve方法時(shí)將回調(diào)中的函數(shù)加入到當(dāng)前的microtask隊(duì)列,優(yōu)先級(jí)要比前者高
  setImmediate(() => {
    if (p.status === STATUS.PENDING) {
      p.stats = STATUS.FULFILLED
      p.value = value
      p.onResolveCallbacks.forEach(fun => {
        fun(p.value)
      })
    }
  })
}

then方法中添加Promise的鏈?zhǔn)秸{(diào)用

之前的MyPromise通過(guò)then方法注冊(cè)事件后,雖然返回了this能夠進(jìn)行鏈?zhǔn)秸{(diào)用,但是如果注冊(cè)的事件返回的是Promise,包含異步的事件則會(huì)出錯(cuò)。針對(duì)這種狀況我們需要進(jìn)行特殊的處理

public then (onFulfilled: Function, onReject?: Function) {
  // 包裝一個(gè)Promise
  let promise2: MyPromise
  // 保存當(dāng)前this——外部的Promise
  const self: MyPromise = this
  // 如果當(dāng)前異步函數(shù)執(zhí)行成功,得到了值
  if (this.status === STATUS.FULFILLED) {
    // 聲明一個(gè)新的 promise2
    promise2 = new MyPromise((resovle, reject) => {
      setImmediate(() => {
        try {
          // then添加的方法返回一個(gè)promise
          let res = onFulfilled(self.value)
          resolvePromise(promise2, res, resovle, reject)
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  if (this.status === STATUS.PENDING) {
    promise2 = new MyPromise((reslove, reject) => {
      self.onResolveCallbacks.push(value => {
        try {
          let res = onFulfilled(value)
          resolvePromise(promise2, res, reslove, reject)
        } catch (error) {
          reject(error)
        }
      })
      self.onRejectCallbacks.push(reason => {
        try {
          let res = onReject(reason)
          resolvePromise(promise2, res, reslove, reject)
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  function resolvePromise (promise: MyPromise, res: any, resolve: Function, reject: Function) {
    // 暫時(shí)不清楚什么情況下會(huì)出現(xiàn)循環(huán)應(yīng)用
    if (promise === res) {
      return reject(new TypeError('循環(huán)引用'))
    }
    let then
    let called
    
    // 如果onFulfilled方法返回的res不為空,并且可能是object(可能是一個(gè)Promise或者一般對(duì)象)或者function
    if (res !== null && ((typeof res === 'object' || typeof res === 'function'))) {
      try {
        then = res.then
        // 如果res具有then屬性,并且是一個(gè)function,說(shuō)明可能是一個(gè)Promise(這里應(yīng)該用類型判斷)
        if (typeof then === 'function') {
          // 重復(fù)調(diào)用then方法, 直到res不再是一個(gè)Promise
          then.call(res, function (res2) {
            // 每次調(diào)用then方法都會(huì)返回一個(gè)新的Promise,如果當(dāng)前的Promise已經(jīng)注冊(cè)過(guò)事件了,將會(huì)直接return
            if (called) return
            called = true
            resolvePromise(promise, res2, resolve, reject)
          }, function (err) {
            if (called) return
            called = true
            reject(err)
          })
        } else {
          // 調(diào)用resolve
          resolve(res)
        }
      } catch (error) {
        if (called) return
        called = true
        reject(error)
      }
    } else {
      resolve(res)
    }
  }
  
  return promise2
}

改進(jìn)后的then方法改進(jìn)了兩個(gè)地方

  1. 判斷通過(guò)then方法注冊(cè)事件時(shí)Promise的狀態(tài),一個(gè)Promise的狀態(tài)應(yīng)該是確定的不可逆的,即只能從PENDING狀態(tài)轉(zhuǎn)換為fulfilled或者reject,當(dāng)調(diào)用then方法注冊(cè)事件時(shí),如果此時(shí)這個(gè)Promise已經(jīng)不是PENDING了,將會(huì)根據(jù)現(xiàn)在的Promise類型執(zhí)行then注冊(cè)的函數(shù)
  2. 每次調(diào)用then方法進(jìn)行函數(shù)注冊(cè)的時(shí)候都會(huì)返回一個(gè)新的Promise,這個(gè)Promise保證了then的鏈?zhǔn)秸{(diào)用。接著我們考慮了注冊(cè)的onFulfilled函數(shù),如果這個(gè)函數(shù)返回的是一個(gè)Promise,則繼續(xù)向它注冊(cè)事件

小結(jié)

  1. Promise本質(zhì)就是一個(gè)發(fā)布訂閱模式,異步函數(shù)是整個(gè)模型的驅(qū)動(dòng)器,完成時(shí)調(diào)用resolve執(zhí)行成功方法,then是向該模型注冊(cè)事件
  2. Promise巧妙的利用發(fā)布訂閱模式,將異步事件的發(fā)生與發(fā)生之后的執(zhí)行解耦了,通過(guò)resolve鉤子觸發(fā)注冊(cè)的函數(shù),使得我們的關(guān)注點(diǎn)在then之后的方法
  3. Typescript用起來(lái)真是爽

這篇文章只是簡(jiǎn)單的介紹了Promise背后執(zhí)行的原理,還有Promise.all Promise.race方法沒(méi)有實(shí)現(xiàn),不過(guò)已經(jīng)不重要了,我們只需要記得Promise是一個(gè)發(fā)布訂閱模式就OK,generator和 async await的方法也沒(méi)有實(shí)現(xiàn)。不過(guò)基于此,可以大膽的猜測(cè)。通過(guò)await執(zhí)行的Promise,是將原本resolve我們注冊(cè)的函數(shù)改為了執(zhí)行await方法中的函數(shù),再把值取出來(lái)給我們調(diào)用。大抵應(yīng)該是這個(gè)原理。實(shí)現(xiàn)上需要寫(xiě)一個(gè)generator runtime這也超過(guò)大部分人的能力。因此能夠用好async await就好了。

本文的源代碼在 Github 歡迎star

本文參考自文章 確認(rèn)過(guò)眼神,你就是我的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)容

  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,835評(píng)論 1 56
  • 本文適用的讀者 本文寫(xiě)給有一定Promise使用經(jīng)驗(yàn)的人,如果你還沒(méi)有使用過(guò)Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,456評(píng)論 6 19
  • 異步編程對(duì)JavaScript語(yǔ)言太重要。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的,如果沒(méi)有異步編程,根本...
    呼呼哥閱讀 7,405評(píng)論 5 22
  • 特別說(shuō)明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 958評(píng)論 0 2
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調(diào)來(lái)表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,249評(píng)論 0 4

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