js Promise實(shí)現(xiàn)筆記

V8引擎的實(shí)現(xiàn)源碼:promise.js
非官方實(shí)現(xiàn),來(lái)自:Promise實(shí)現(xiàn)原理(附源碼)
注:啃官方源碼和其他原型鏈的實(shí)現(xiàn)有點(diǎn)煩躁,找了個(gè)用class語(yǔ)法糖的源碼實(shí)現(xiàn)德召,下面代碼主要是這個(gè)非官方實(shí)現(xiàn)



快速答幾個(gè)問題先:
1.setTimeout設(shè)置0延時(shí)白魂,并不是同步執(zhí)行,而是交由定時(shí)器線程計(jì)時(shí)并在指定時(shí)間插入到j(luò)s線程的消息隊(duì)列上岗,達(dá)到延遲(異步)執(zhí)行福荸。關(guān)于chrome的最小1ms延遲參見:定時(shí)器(setTimeout/setInterval)最小延遲的問題
2.實(shí)際Promise中的resolvereject有這么神奇的表現(xiàn),其實(shí)就是對(duì)setTimeout的一層封裝而實(shí)現(xiàn)異步(官方實(shí)現(xiàn)有出入)
3.then的鏈?zhǔn)秸{(diào)用肴掷,實(shí)際是每次返回一個(gè)全新的Promise敬锐,其新的Promise對(duì)象在構(gòu)造時(shí)完成對(duì)前一個(gè)Promise的處理。
4.源碼帶上all呆瞻、race這些方法也就恰好199行台夺,不看我廢話其實(shí)挺少的

正文

非官方代碼:

// 判斷變量否為function
const isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態(tài)常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(handle) {
    if (!isFunction(handle)) {
      throw new Error('MyPromise must accept a function as a parameter')
    }
    // 添加狀態(tài)
    this._status = PENDING
    // 添加狀態(tài)
    this._value = undefined
    // 添加成功回調(diào)函數(shù)隊(duì)列
    this._fulfilledQueues = []
    // 添加失敗回調(diào)函數(shù)隊(duì)列
    this._rejectedQueues = []
    // 執(zhí)行handle
    try {
      handle(this._resolve.bind(this), this._reject.bind(this))
    } catch (err) {
      this._reject(err)
    }
  }
  // 添加resovle時(shí)執(zhí)行的函數(shù)
  _resolve(val) {
    const run = () => {
      if (this._status !== PENDING) return
      // 依次執(zhí)行成功隊(duì)列中的函數(shù),并清空隊(duì)列
      const runFulfilled = (value) => {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      // 依次執(zhí)行失敗隊(duì)列中的函數(shù)痴脾,并清空隊(duì)列
      const runRejected = (error) => {
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }
      /* 如果resolve的參數(shù)為Promise對(duì)象颤介,則必須等待該P(yáng)romise對(duì)象狀態(tài)改變后,
        當(dāng)前Promsie的狀態(tài)才會(huì)改變,且狀態(tài)取決于參數(shù)Promsie對(duì)象的狀態(tài)
      */
      if (val instanceof MyPromise) {
        val.then(value => {
          this._value = value
          this._status = FULFILLED
          runFulfilled(value)
        }, err => {
          this._value = err
          this._status = REJECTED
          runRejected(err)
        })
      } else {
        this._value = val
        this._status = FULFILLED
        runFulfilled(val)
      }
    }
    // 為了支持同步的Promise赞赖,這里采用異步調(diào)用
    setTimeout(run, 0)
  }
  // 添加reject時(shí)執(zhí)行的函數(shù)
  _reject(err) {
    if (this._status !== PENDING) return
    // 依次執(zhí)行失敗隊(duì)列中的函數(shù)滚朵,并清空隊(duì)列
    const run = () => {
      this._status = REJECTED
      this._value = err
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(err)
      }
    }
    // 為了支持同步的Promise,這里采用異步調(diào)用
    setTimeout(run, 0)
  }
  // 添加then方法
  then(onFulfilled, onRejected) {
    const { _value, _status } = this
    // 返回一個(gè)新的Promise對(duì)象
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封裝一個(gè)成功時(shí)執(zhí)行的函數(shù)
      let fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value)
          } else {
            let res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 如果當(dāng)前回調(diào)函數(shù)返回MyPromise對(duì)象前域,必須等待其狀態(tài)改變后在執(zhí)行下一個(gè)回調(diào)
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否則會(huì)將返回結(jié)果直接作為參數(shù)辕近,傳入下一個(gè)then的回調(diào)函數(shù),并立即執(zhí)行下一個(gè)then的回調(diào)函數(shù)
              onFulfilledNext(res)
            }
          }
        } catch (err) {
          // 如果函數(shù)執(zhí)行出錯(cuò)匿垄,新的Promise對(duì)象的狀態(tài)為失敗
          onRejectedNext(err)
        }
      }
      // 封裝一個(gè)失敗時(shí)執(zhí)行的函數(shù)
      let rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error)
          } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果當(dāng)前回調(diào)函數(shù)返回MyPromise對(duì)象亏推,必須等待其狀態(tài)改變后在執(zhí)行下一個(gè)回調(diào)
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否則會(huì)將返回結(jié)果直接作為參數(shù),傳入下一個(gè)then的回調(diào)函數(shù)年堆,并立即執(zhí)行下一個(gè)then的回調(diào)函數(shù)
              onFulfilledNext(res)
            }
          }
        } catch (err) {
          // 如果函數(shù)執(zhí)行出錯(cuò)吞杭,新的Promise對(duì)象的狀態(tài)為失敗
          onRejectedNext(err)
        }
      }
      switch (_status) {
        // 當(dāng)狀態(tài)為pending時(shí),將then方法回調(diào)函數(shù)加入執(zhí)行隊(duì)列等待執(zhí)行
        case PENDING:
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
          break
        // 當(dāng)狀態(tài)已經(jīng)改變時(shí)变丧,立即執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)
        case FULFILLED:
          fulfilled(_value)
          break
        case REJECTED:
          rejected(_value)
          break
      }
    })
  }
  // 添加catch方法
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
  // 添加靜態(tài)resolve方法
  static resolve(value) {
    // 如果參數(shù)是MyPromise實(shí)例芽狗,直接返回這個(gè)實(shí)例
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
  // 添加靜態(tài)reject方法
  static reject(value) {
    return new MyPromise((resolve, reject) => reject(value))
  }
  // 添加靜態(tài)all方法
  static all(list) {
    return new MyPromise((resolve, reject) => {
      /**
       * 返回值的集合
       */
      let values = []
      let count = 0
      for (let [i, p] of list.entries()) {
        // 數(shù)組參數(shù)如果不是MyPromise實(shí)例,先調(diào)用MyPromise.resolve
        this.resolve(p).then(res => {
          values[i] = res
          count++
          // 所有狀態(tài)都變成fulfilled時(shí)返回的MyPromise狀態(tài)就變成fulfilled
          if (count === list.length) resolve(values)
        }, err => {
          // 有一個(gè)被rejected時(shí)返回的MyPromise狀態(tài)就變成rejected
          reject(err)
        })
      }
    })
  }
  // 添加靜態(tài)race方法
  static race(list) {
    return new MyPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一個(gè)實(shí)例率先改變狀態(tài)痒蓬,新的MyPromise的狀態(tài)就跟著改變
        this.resolve(p).then(res => {
          resolve(res)
        }, err => {
          reject(err)
        })
      }
    })
  }
  finally(cb) {
    return this.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason => MyPromise.resolve(cb()).then(() => { throw reason })
    );
  }
}

看代碼(類名我直接當(dāng)作是Promise了童擎,請(qǐng)注意),按步驟分析主要流程(_resolve攻晒、then):
1.Promise構(gòu)造函數(shù)中先初始化4個(gè)成員變量顾复,其中
this._status代表當(dāng)前狀態(tài)
this._value用來(lái)存著結(jié)果值
this._fulfilledQueues和另外一個(gè),用來(lái)存著稍后then方法可能push進(jìn)來(lái)的封裝過(guò)的回調(diào)方法

2.Promise構(gòu)造函數(shù)中馬上調(diào)用傳入的handle函數(shù)鲁捏,給他傳入我們"封裝了setTimeout"的_resolve方法芯砸。_resolve即我們new Promise( function(resolve,reject){resolve(10)} )的那個(gè)resolve。這個(gè)方法只干兩件事:暴露它的參數(shù)10給內(nèi)部run方法;setTimeout(run,0)來(lái)異步調(diào)用run方法假丧。
而內(nèi)部run方法主要干三件事情:修改this._status; 修改this._value;從this._fulfilledQueues(或另一個(gè)隊(duì)列)中取出全部函數(shù)并逐一調(diào)用(即cb(value))双揪。

小結(jié):當(dāng)value是Promise對(duì)象時(shí),run還會(huì)做一層處理包帚,此類細(xì)節(jié)不再敘述渔期,看源碼比較不啰嗦。此處已能夠幫助我們理解new Promise時(shí)發(fā)生了什么渴邦;為什么函數(shù)會(huì)被執(zhí)行但是resolve處又會(huì)跳過(guò)等等“特性”疯趟,都已經(jīng)非常清晰。

3.then方法:
上面流程做完谋梭,就等著then方法被調(diào)用了信峻。此時(shí)then方法中會(huì)new一個(gè)Promise返回,而new Promise需要一個(gè)handle函數(shù)當(dāng)參數(shù)章蚣,所以我們只需要關(guān)注這個(gè)then方法中怎么寫這個(gè)“默認(rèn)handle函數(shù)”就行了(注意站欺,new Promise中handle是會(huì)被同步執(zhí)行的,前面提到了)纤垂。

很顯然矾策,“默認(rèn)handle”主要只干一件事情:判斷如何調(diào)用內(nèi)部的fulfilled(value)函數(shù)。
fulfilled主要干兩件事情:調(diào)用then的參數(shù)函數(shù)"onFulfilled(value)"峭沦;調(diào)用新Promise對(duì)象的參數(shù)函數(shù)"onFulfilledNext(value)"(即新Promise對(duì)象中的_resolve)贾虽。value的處理邏輯看代碼不廢話了。

判斷部分:
1.同步調(diào)用then吼鱼,此時(shí)Promise狀態(tài)為PENDING蓬豁,也就是run尚未被調(diào)用(只有這個(gè)方法會(huì)改變狀態(tài)的值)。那么我們把fulfilled送進(jìn)隊(duì)列就行了菇肃,待會(huì)run被調(diào)用時(shí)地粪,會(huì)清空我們的隊(duì)列并完成調(diào)用。
2.異步調(diào)用then琐谤,如setTimeout(obj.then,100,...省略)蟆技。此時(shí)我們的run可能被調(diào)用了,從而狀態(tài)為FULFILLED斗忌,意味著this._value已經(jīng)也被run給修改了质礼,那么直接調(diào)用fulfilled(_value)(注意this指向問題)

鏈?zhǔn)秸{(diào)用then:
第一個(gè)Promise的then中,最終會(huì)調(diào)用第二個(gè)Promise的_resolve织阳,從而啟動(dòng)第二個(gè)Promise的setTimeout(run,0)......剩下就是重復(fù)的邏輯和流程眶蕉,當(dāng)調(diào)用第二個(gè)Promise的then時(shí),把回調(diào)方法同樣封裝后插進(jìn)它的隊(duì)列里面(或異步then導(dǎo)致可能直接執(zhí)行而不進(jìn)隊(duì)列唧躲,就上面的步驟)造挽。


必看

micro-task和macro-task碱璃,即微任務(wù)和宏任務(wù):知乎 Promise的隊(duì)列與setTimeout的隊(duì)列有何關(guān)聯(lián)?

setTimeout(function () { console.log(4) }, 0);
new Promise(function (resolve) {
    console.log(1)
    for (var i = 0; i < 10000; i++) {
        i == 9999 && resolve()
    }
    console.log(2)
}).then(function () { console.log(5) });
console.log(3);

結(jié)果是1,2,3,5,4刽宪。如果改成上面我們的MyPromise厘贼,結(jié)果是1,2,3,4,5界酒。
原因在于V8引擎中圣拄,用的是%EnqueueMicrotask,是微任務(wù)毁欣;我們這里是直接setTimeout庇谆,是宏任務(wù)。而micro-task和macro-task這兩個(gè)隊(duì)列的執(zhí)行邏輯如下:

這里提及了 macrotask 和 microtask 兩個(gè)概念凭疮,這表示異步任務(wù)的兩種分類饭耳。在掛起任務(wù)時(shí),JS 引擎會(huì)將所有任務(wù)按照類別分到這兩個(gè)隊(duì)列中执解,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù)寞肖,執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行;之后再取 macrotask 任務(wù)衰腌,周而復(fù)始新蟆,直至兩個(gè)隊(duì)列的任務(wù)都取完。
macro-task: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, Promises(這里指瀏覽器實(shí)現(xiàn)的原生 Promise), Object.observe, MutationObserver

詳細(xì)點(diǎn)進(jìn)知乎鏈接吧右蕊,不廢話了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宾濒,更是在濱河造成了極大的恐慌宠进,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萝风,死亡現(xiàn)場(chǎng)離奇詭異嘀掸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)规惰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門睬塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人卿拴,你說(shuō)我怎么就攤上這事衫仑。” “怎么了堕花?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵文狱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缘挽,道長(zhǎng)瞄崇,這世上最難降的妖魔是什么呻粹? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮苏研,結(jié)果婚禮上等浊,老公的妹妹穿的比我還像新娘。我一直安慰自己摹蘑,他們只是感情好筹燕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衅鹿,像睡著了一般撒踪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上大渤,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天制妄,我揣著相機(jī)與錄音,去河邊找鬼泵三。 笑死耕捞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烫幕。 我是一名探鬼主播俺抽,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纬霞!你這毒婦竟也來(lái)了凌埂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诗芜,失蹤者是張志新(化名)和其女友劉穎瞳抓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伏恐,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孩哑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翠桦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片横蜒。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖销凑,靈堂內(nèi)的尸體忽然破棺而出丛晌,到底是詐尸還是另有隱情,我是刑警寧澤斗幼,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布澎蛛,位于F島的核電站,受9級(jí)特大地震影響蜕窿,放射性物質(zhì)發(fā)生泄漏谋逻。R本人自食惡果不足惜呆馁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毁兆。 院中可真熱鬧浙滤,春花似錦、人聲如沸气堕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)送巡。三九已至摹菠,卻和暖如春盒卸,著一層夾襖步出監(jiān)牢的瞬間骗爆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蔽介, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摘投,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓虹蓄,卻偏偏與公主長(zhǎng)得像犀呼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子薇组,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 弄懂js異步 講異步之前外臂,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,710評(píng)論 0 5
  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案律胀,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,705評(píng)論 1 56
  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗(yàn)的人宋光,如果你還沒有使用過(guò)Promise,這篇文章可能不適合你炭菌,...
    HZ充電大喵閱讀 7,305評(píng)論 6 19
  • 原文地址https://fancierpj0.github.io/iPromise/ 目錄 (づ ̄ 3 ̄)づ=> ...
    Cirs_冷崢子閱讀 1,374評(píng)論 3 6
  • 一罪佳、Promise的含義 Promise在JavaScript語(yǔ)言中早有實(shí)現(xiàn),ES6將其寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn)黑低,統(tǒng)一了用法...
    Alex灌湯貓閱讀 824評(píng)論 0 2