從零實現(xiàn)一個 Promise

Promise 作為由社區(qū)提出和實現(xiàn)的異步編程解決方案塘砸,ES6 將其寫進了語言標準,統(tǒng)一了用法确封,原生提供了 Promise 對象需五。本文將剖析 Promise 內部標準鹉动,根據 Promises/A+ 規(guī)范從零實現(xiàn)一個 Promise。

Promise 構造函數(shù)

在 Promise 構造函數(shù)中宏邮,主要操作是初始化狀態(tài)和數(shù)據以及執(zhí)行函數(shù)參數(shù)泽示。

首先需要將狀態(tài)初始化為 pending,然后定義 Promise 的值以及回調函數(shù)集蜜氨。

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(executor) {
  let self = this
  self.status = PENDING // Promise 狀態(tài)械筛,初始狀態(tài)為 pending
  self.data = undefined  // Promise 的值
  self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
  self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集

  // 待完善,resolve 和 reject 函數(shù)
  // 待完善记劝,執(zhí)行 executor 函數(shù)
}

在構造函數(shù)中变姨,還需要執(zhí)行由外部傳進來的 executor 函數(shù)族扰,executor 函數(shù)中有兩個函數(shù)參數(shù)厌丑,分別為 resolve 和 reject 函數(shù)。

function MyPromise(executor) {
  let self = this
  self.status = PENDING // Promise 狀態(tài)渔呵,初始狀態(tài)為 pending
  self.data = undefined  // Promise 的值
  self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
  self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集

  function resolve(value) {
    // 當狀態(tài)為 pending 時怒竿,改變狀態(tài)為 resolved,存儲 Promise 值以及執(zhí)行回調函數(shù)集
    if (self.status === PENDING) {
      self.status = RESOLVED
      self.data = value
      self.onResolvedCallback.map(cb => cb(value))
    }
  }

  function reject(reason) {
    // 當狀態(tài)為 pending 時扩氢,改變狀態(tài)為 rejected耕驰,存儲 Promise 值以及執(zhí)行回調函數(shù)集
    if (self.status === PENDING) {
      self.status = REJECTED
      self.data = reason
      self.onRejectedCallback.map(cb => cb(reason))
    }
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    // executor 函數(shù)執(zhí)行中拋出錯誤時該 Promise 應該被 reject
    reject(e)
  }
}

executor 函數(shù)需要使用 try catch 包裹執(zhí)行的原因則是在 executor 函數(shù)執(zhí)行中可能會拋出錯誤,當拋出錯誤時則該 Promise 應該被 reject录豺,如下情況:

// 該 Promise 應該被 reject
new Promise(function(resolve, reject) {
  throw 2
})
then 方法

then 方法主要是根據 Promise 當前狀態(tài)處理相應的邏輯朦肘,返回一個新的 Promise饭弓,新 Promise 的狀態(tài)由原先 Promise 的狀態(tài)和 then 方法函數(shù)參數(shù)中的返回值決定。

當 then 方法執(zhí)行時媒抠,該 Promise 的狀態(tài)是不確定的弟断,所以需要對 Promise 的狀態(tài)進行判斷然后執(zhí)行不同的操作,then 方法會返回一個新的 Promise趴生。

MyPromise.prototype.then = function (onResolved, onRejected) {
  let self = this
  let promise2

  // 如果 then 的參數(shù)不是 function阀趴,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
  onResolved = typeof onResolved === 'function' ? onResolved : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

  if (self.status === RESOLVED) {
    return promise2 = new MyPromise((resolve, reject) => {
      
    })
  }

  if (self.status === REJECTED) {
    return promise2 = new MyPromise((resolve, reject) => {
      
    })
  }

  if (self.status === PENDING) {
    return promise2 = new MyPromise((resolve, reject) => {
      
    })
  }
}

then 方法會返回一個新的 Promise 后,新 Promise 的狀態(tài)由原先 Promise 的狀態(tài)和 then 方法函數(shù)參數(shù)中的返回值決定苍匆。

MyPromise.prototype.then = function (onResolved, onRejected) {
  let self = this
  let promise2

  // 如果 then 的參數(shù)不是 function刘急,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
  onResolved = typeof onResolved === 'function' ? onResolved : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

  if (self.status === RESOLVED) {
    return promise2 = new MyPromise((resolve, reject) => {
      try {
        // 執(zhí)行 onResolved 函數(shù)并獲取返回值。若返回值是 Promise 對象浸踩,則取它的結果作為 promise2 的結果叔汁,否則以返回值作為 promise2 的結果
        var x = onResolved(self.data)
        if (x instanceof MyPromise) {
          x.then(resolve, reject)
        }
        resolve(x)
      } catch (e) {
        // 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
        reject(e)
      }
    })
  }

  if (self.status === REJECTED) {
    return promise2 = new MyPromise((resolve, reject) => {
      try {
        // 執(zhí)行 onRejected 函數(shù)并獲取返回值。若返回值是 Promise 對象检碗,則取它的結果作為 promise2 的結果攻柠,否則以返回值作為 promise2 的結果
        var x = onRejected(self.data)
        if (x instanceof MyPromise) {
          x.then(resolve, reject)
        }
        reject(x)
      } catch (e) {
        // 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
        reject(e)
      }
    })
  }

  if (self.status === PENDING) {
    return promise2 = new MyPromise((resolve, reject) => {
      // 將回調函數(shù)存進回調函數(shù)集
      self.onResolvedCallback.push((value) => {
        try {
          var x = onResolved(self.data)
          if (x instanceof MyPromise) {
            x.then(resolve, reject)
          }
          resolve(x)
        } catch (e) {
          reject(e)
        }
      })
      self.onRejectedCallback.push((reason) => {
        try {
          var x = onRejected(self.data)
          if (x instanceof MyPromise) {
            x.then(resolve, reject)
          } 
          reject(x)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

在 then 方法中根據 Promise 的當前狀態(tài)分別執(zhí)行了不同的操作。當狀態(tài)為 resolved 時后裸,執(zhí)行 onResolved 函數(shù)(then 方法第一個函數(shù)參數(shù))并根據返回值確定 promise2 的狀態(tài)瑰钮;當狀態(tài)為 rejected 時,執(zhí)行 onRejected 函數(shù)(then 方法第二個函數(shù)參數(shù))并根據返回值確定 promise2 的狀態(tài)微驶;當狀態(tài)為 pending 時浪谴,則需要將 onResolved 和 onRejected 函數(shù)先存進回調函數(shù)集中,等到 Promise 狀態(tài)改變后再執(zhí)行因苹。

而在代碼注釋中說明苟耻,如果 then 的參數(shù)不是 function,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳扶檐。

當傳進 then 方法中 onResolved 或 onRejected 函數(shù)參數(shù)為空時凶杖,則應該賦予它們一個默認函數(shù),該默認函數(shù) return 或 throw 原先的參數(shù)值款筑,這樣才能正確實現(xiàn) then 方法的鏈式調用智蝠,如下:

new MyPromise((resolve, reject) => { resolve(1) })
  .then()
  .then()
  .then((value) => {
    console.log(value)
  })

至此,我們便完成了一個符合 Promises/A+ 規(guī)范的 Promise 基礎版奈梳,同原生 Promise 一樣杈湾,可以通過如下方式使用:

let myPromise = new MyPromise((resolve, reject) => {
  if (/* 異步操作成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
});

myPromise.then((value) => {
  console.log(value)
}, (err) => {
  console.log(err)
})
Promise 終極版

上述的代碼已經能夠實現(xiàn)一個基本 Promise 的功能,而在實際使用過程中攘须,我們還需要根據 Promises/A+ 規(guī)范繼續(xù)完善它漆撞。

需要完善的主要有以下兩點:

  1. 不同 Promise 之間的兼容;
  2. 異步調用操作;

在實際中浮驳,有多種不同的 Promise 實現(xiàn)悍汛,關于不同 Promise 間的交互, Promises/A+ 規(guī)范已經做了詳細的說明至会,其中詳細指定了如何通過 then 的實參的返回值來決定 promise2 的狀態(tài)员凝,我們只需要按照標準將內容轉成代碼即可。

function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) { 
    if (x.status === 'pending') {
      x.then(function(value) {
        resolvePromise(promise2, value, resolve, reject)
      }, reject)
    } else { 
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { 
    try {
      then = x.then 
      if (typeof then === 'function') {
        then.call(x, function rs(y) { 
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject) 
        }, function rj(r) { 
          if (thenCalledOrThrow) return 
          thenCalledOrThrow = true
          return reject(r)
        })
      } else { 
        resolve(x)
      }
    } catch (e) { 
      if (thenCalledOrThrow) return 
      thenCalledOrThrow = true
      return reject(e)
    }
  } else { 
    resolve(x)
  }
}

所以奋献,在 then 方法中健霹,我們不再需要判斷返回值 x 的類型,然后再根據 x 的類型去決定 promise2 的狀態(tài)瓶蚂,只需要將其傳入 resolvePromise 函數(shù)即可糖埋。

// self.status === RESOLVED 部分更改,其余兩個狀態(tài)更改同理
var x = onResolved(self.data)
if (x instanceof MyPromise) {
  x.then(resolve, reject)
}
resolve(x)
=>
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)

最后窃这,在標準中瞳别,說明了某些地方需要使用異步調用,在我們的實現(xiàn)中杭攻,我們需要在 resolve祟敛、reject、onResolved兆解、onRejected 加上異步調用的代碼馆铁,這里我們使用 setTimeout(fn, 0) 來實現(xiàn)。

至此锅睛,我們實現(xiàn)了一個符合 Promises/A+ 規(guī)范的終極版 Promise埠巨,如下:

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(executor) {
  let self = this
  self.status = PENDING // Promise 狀態(tài),初始狀態(tài)為 pending
  self.data = undefined  // Promise 的值
  self.onResolvedCallback = [] // Promise resolve 時的回調函數(shù)集
  self.onRejectedCallback = [] // Promise reject 時的回調函數(shù)集

  function resolve(value) {
    setTimeout(() => { // 異步回調
      // 當狀態(tài)為 pending 時现拒,改變狀態(tài)為 resolved辣垒,存儲 Promise 值以及執(zhí)行回調函數(shù)集
      if (self.status === PENDING) {
        self.status = RESOLVED
        self.data = value
        self.onResolvedCallback.map(cb => cb(value))
      }
    }, 0)
  }

  function reject(reason) {
    setTimeout(() => { // 異步回調
      // 當狀態(tài)為 pending 時,改變狀態(tài)為 rejected印蔬,存儲 Promise 值以及執(zhí)行回調函數(shù)集
      if (self.status === PENDING) {
        self.status = REJECTED
        self.data = reason
        self.onRejectedCallback.map(cb => cb(reason))
      }
    }, 0)
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    // executor 函數(shù)執(zhí)行中拋出錯誤時該 Promise 應該被 reject
    reject(e)
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  let self = this
  let promise2

  // 如果 then 的參數(shù)不是 function勋桶,則我們需要賦予默認函數(shù)實現(xiàn)值的透傳
  onResolved = typeof onResolved === 'function' ? onResolved : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

  if (self.status === RESOLVED) {
    return promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => { // 異步回調
        try {
          // 執(zhí)行 onResolved 函數(shù)并獲取返回值。若返回值是 Promise 對象例驹,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          // 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
          reject(e)
        }
      }, 0)
    })
  }

  if (self.status === REJECTED) {
    return promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => { // 異步回調
        try {
          // 執(zhí)行 onRejected 函數(shù)并獲取返回值眠饮。若返回值是 Promise 對象,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          // 拋出錯誤則以捕獲到的錯誤作為 promise2 的結果
          reject(e)
        }
      }, 0)
    })
  }

  if (self.status === PENDING) {
    return promise2 = new MyPromise((resolve, reject) => {
      // 將回調函數(shù)存進回調函數(shù)集
      self.onResolvedCallback.push((value) => {
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
      self.onRejectedCallback.push((reason) => {
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') {
      x.then(function (value) {
        resolvePromise(promise2, value, resolve, reject)
      }, reject)
    } else {
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then
      if (typeof then === 'function') {
        then.call(x, function rs(y) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject)
        }, function rj(r) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}

參考文章:
https://github.com/xieranmaya/blog/issues/2

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末松蒜,一起剝皮案震驚了整個濱河市扔茅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秸苗,老刑警劉巖召娜,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惊楼,居然都是意外死亡玖瘸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門檀咙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雅倒,“玉大人,你說我怎么就攤上這事弧可∶锵唬” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵棕诵,是天一觀的道長裁良。 經常有香客問我,道長校套,這世上最難降的妖魔是什么价脾? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮笛匙,結果婚禮上彼棍,老公的妹妹穿的比我還像新娘。我一直安慰自己膳算,他們只是感情好座硕,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涕蜂,像睡著了一般华匾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上机隙,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天蜘拉,我揣著相機與錄音,去河邊找鬼有鹿。 笑死旭旭,一個胖子當著我的面吹牛,可吹牛的內容都是我干的葱跋。 我是一名探鬼主播持寄,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼源梭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稍味?” 一聲冷哼從身側響起废麻,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎模庐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怜姿,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡疼燥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年悴了,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熟空。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡息罗,死狀恐怖才沧,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情温圆,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布得运,位于F島的核電站熔掺,受9級特大地震影響非剃,放射性物質發(fā)生泄漏。R本人自食惡果不足惜券坞,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望报慕。 院中可真熱鬧压怠,春花似錦、人聲如沸蜗顽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔挖。三九已至庵寞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脓鹃,已是汗流浹背古沥。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工岩齿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盹沈。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓襟诸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親歌亲。 傳聞我的和親對象是個殘疾皇子菇用,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容