自己鼓搗一個Promise

利維亞的傑洛特發表於2019-04-18

image

原始碼

very-simple-promise,包含了不完善的單元測試❤️.

感謝

程式碼君的自白

這篇的文章主要參考了上面的部落格,謝謝他的幫助?。

Promise/A+規範

Promises/A+規範, 下面?是對規範的內容的部分翻譯。英文比較爛,不喜歡的可以不看

承諾必須滿足三種狀態, pending(等處理), fulfilled(履行), rejected(拒絕)

Promises的狀態

  • promise處於pending時
  • 可以過渡到fulfilled或者rejected狀態
  • promise處於fulfilled時
  • 不能向其他狀態過渡
  • promise處於rejected時
  • 不能向其他狀態過渡

then

promise的then方法接受兩個引數

promise.then(onFulfilled, onRejected)
複製程式碼
  • onFulfilled和onRejected都是可選引數
    • onFulfilled不是函式需要忽略
    • onRejected不是函式需要忽略
  • onFulfilled如果是一個函式
    • onFulfilled在promise狀態為fulfilled時被呼叫
    • onFulfilled只能被呼叫一次
    • onFulfilled不能在fulfilled之前不能呼叫
  • onRejected如果是一個函式
    • onRejected在promise狀態為rejected時被呼叫
    • 在rejected之前不能呼叫它
    • onRejected只能被呼叫一次
  • onFulfilled or onRejected must not be called until the execution context stack contains only platform code. (原文不太懂什麼意思,但是規範給出了註解)
  • onFulfilled和onRejected必須作為函式呼叫
  • then可以在同一個promise多次呼叫
    • 如果promise被履行, 則onFulfilled回撥必須按照then的順序執行
    • 如果promise被拒絕, 則onRejected回撥必須按照then的順序執行
promise2 = promise1.then(onFulfilled, onRejected);
複製程式碼
  • then必須返回promise
    • 如果onFulfilled或者onRejected返回一個值,則執行處理Promise的過程[[Resolve]](promise2, x)(這裡處理的Resolve(promise2, x), onFulfilled或者onRejected的返回值會被resolve處理, resolve的第一個引數是then返回的新的promise)
    • 如果onFulfilled, onRejected丟擲錯誤error, 則promise2必須為rejected
    • 如果onFulfilled不是一個函式, 而promise1的狀態為fulfilled, promise2必須與promise1具有相同的值被履行
    • 如果onRejected不是一個函式, 而promise1的狀態為rejected, promise2必須與promise1具有相同的值被拒絕

處理Promise

[[Resolve]](promise, x)需要遵循以下規範

  • 如果promise與x相等, 丟擲TypeError的錯誤, 並以錯誤為理由拒絕promise
  • 如果x是Promise
    • 如果x處於pending,promise也需要保持pending,直到x接受pending
    • 如果x處於fulfilled,promise需要使用相同值執行promise
    • 如果x處於rejected,promise需要使用相同的值拒絕promise
  • 如果x是物件或者函式
    • 將x.then賦於then
    • 如果獲取x.then時丟擲錯誤,則使用錯誤作為理由拒絕promise
    • 如果then是函式?️
      • 使用x作為then函式的作用域中的this, 傳入兩個引數作為回撥,分別是resolvePromise, 和rejectPromise
      • 如果resolvePromise如果以y為引數執行,則執行[[Resolve]](promise, y), 履行promise。resolve(promise,y)
      • 如果rejectPromise以r為引數執行,則以r為原因拒絕promise。reject(promise, r)
      • 如果then丟擲了錯誤
        • 如果resolvePromise,rejectPromise已經呼叫對錯誤忽略
        • 如果沒有呼叫,用錯誤作為原因拒絕promise
    • 如果then不是函式,使用x作為引數執行promise
  • 如果x不是物件也不是函式,則使用x作為引數執行promise

註解

onFulfilled和onRejected方法應當是非同步執行,且應該在then方法被呼叫的那一輪事件迴圈之後的微任務執行棧中執行。可以使用巨集任務macrotask和微任務microtask機制實現。也就是說巨集任務完成後(Promise的then呼叫後),會清空微任務佇列(執行onFulfilled或者onRejected)。

macrotask包含:script(整體程式碼)、setTimeout、setInterval、I/O、UI互動事件、postMessage 、requestAnimationFrame 、MessageChannel、etImmediate(Node.js 環境)

microtask包含:Promise.then、setImmediate、MutaionObserver、process.nextTick(Node.js 環境)

事件迴圈

下面?是一個關於事件迴圈的例子?

image

Promise中的引數函式,應當是立即呼叫的。我們這時先執行了resolve(1), 這會將外層的Promise的狀態置為fulfilled態。但是這時外層的then還沒有執行。

我們接下來執行了Promise.resolve().then(() => console.log(2)), 我們將then的onFulfilled,push到了macrotask佇列中,會在巨集任務執行完成後清空微任務。

執行外層Promise的then, onFulfilled, push到了macrotask佇列中。

接著執行console.log(3), 然後清空macrotask佇列,執行2,1(佇列先進先出)

Promise建構函式


const pending = 0
const fulfilled = 1
const rejected = 2

function reject (promise, result) {}

function resolve (promise, reason) {}

export default class Promise {
  constructor (fn) {
    this.fn = fn
    // Promise的狀態, 初始狀態為pending
    this._state = pending
    // Promise的fulfilled態的原因,或者Promise的rejected態的理由
    this._value = null
    // 儲存then的佇列
    this._tasks = []
    // 建立Promise物件後,立即執行fn
    this.init()
  }

  init () {
    try {
      // 執行fn, 傳入包裝後的resolve,reject
      // reject,resolve會修改promise的狀態
      this.fn(
        (result) => resolve(this, result),
        (reason) => reject(this, reason)
      )
    } catch (error) {
      reject(this)
    }
  }
}
複製程式碼

根據規範2.2.6: then may be called multiple times on the same promisethen可以在同一個promise中多次呼叫。所以我們用一個陣列儲存then的結果。

Promise.prototype.then

根據規範2.2.1: A promise’s then method accepts two arguments. Both onFulfilled and onRejected are optional arguments。then接受兩個引數, 兩個引數可選。

根據規範2.2.1.1, 2.2.1.2: If onFulfilled is not a function, it must be ignored. If onRejected is not a function, it must be ignored.。onFulfilled, onRejected必須是函式負責會被忽略。

根據規範2.2.2, 2.2.3, onFulfilled必須在狀態為fulfilled呼叫, onRejected必須在狀態為rejected呼叫

根據規範2.2.7: then must return a promise。then必須返回一個promise。

?️ 為什麼不能返回當前的promise?因為根據規範2.1.2, 2.1.3, promise被修改狀態後,不能再次修改狀態。我們如果需要執行then的callback需要修改promise的狀態。

我們使用task封裝?then的回撥以及then返回的promise。


class Task {
  constructor (onFulfilled, onRejected, promise) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = null
    }
    if (typeof onRejected !== 'function') {
      onRejected = null
    }
    this.onFulfilled = onFulfilled
    this.onRejected = onRejected
    this.promise = promise
  }
}

class Promise {

  // ...

  then (onFulfilled, onRejected) {
    
    let nextPromise = new Promise(function () {})

    let task = new Task(onFulfilled, onRejected, nextPromise)

    if (this._state === pending) {
      this._tasks.push(task)
    } else {
      handlePromise(this, task)
    }

    // 返回新的promise
    return nextPromise
  }
}

複製程式碼

Promise.prototype.catch

catch函式同樣會返回新的promise, 但是在建立task時,我們不會傳入onFulfilled引數。所以當promise當為fulfilled態,雖然catch的回撥同樣存放_task中,但是由於callback為null, 在handlePromise中會向下穿透。

class Promise {
  // ...
  catch (onRejected) {
    let nextPromise = new Promise(function () {})

    // onFulfilled設定為null
    let task = new Task(null, onRejected, nextPromise)

    if (this._state === pending) {
      this._tasks.push(task)
    } else {
      handlePromise(this, task)
    }

    // 返回新的promise
    return nextPromise
  }
}
複製程式碼

Promise.prototype.finally

finally方法用於指定不管Promise物件最後狀態如何,都會執行的操作。

我們無論promise的狀態如何,都在promise的最後面新增then,並傳入了onFulfilled, onRejected兩個回撥。在回撥中,我們執行finally的callback引數。這樣無論之前的promise是fulfilled態,還是rejected態,都會執行finally新增的引數。


class Promise {
  // ...

  finally (callback) {
    // this指向呼叫finally的物件
    const self = this
    // 向Promise鏈中新增then,無論,promise是resolve態還是reject態,都會執行callback
    // 並且會通過then,繼續將result或reason向下傳遞
    return self.then(
      result => Promise.resolve(callback()).then(_ => result),
      reason => Promise.resolve(callback()).then(_ => { throw reason })
    )
  }
}
複製程式碼

resolve

resolve用來修改promise狀態,將promise狀態設定為fulfilled態, 並執行then的onFulfilled回撥

根據規範**2.3.1: If promise and x refer to the same object, reject promise with a TypeError as the reason.**如果promise與x相等,我們使用TypeError的錯誤拒絕promise

根據規範2.3.2。如果result是promise,並且處於pending態,promise需要保持pending態,直到result被執行和拒絕後,我們使用result的狀態履行或者拒絕promise。如果result是promise,並且處於fulfilled或rejected態,我們使用result的狀態拒絕或者履行promise。

根據規範2.3.3, 我們判斷result是否為一個Object。如果result為Object, 我們則取出它的then的屬性, 判斷then屬性是否為Function, 如果then為Function, 我們設定then的作用域的this指向result, 我們傳入resolvePromise, rejectPromise作為引數。

根據規範2.3.4: If x is not an object or function, fulfill promise with x, 如果x不是函式或者物件,我們用result結果作為引數執行promise。


function resolve (promise, result) {
  if (promise === result) {
    throw reject(promise, new TypeError('promise and x refer to the same object'))
  }

  if (isPromise(result)) {
    if (result._state === pending) {
      result._tasks.concat(promise._tasks)
    } else if (result._state === fulfilled || result._state === rejected) {
      let task
      while (task = promise._tasks.shift()) {
        handlePromise(result, task)
      }
    }
    return
  }

  if (isObject(result)) {

    let then = null

    try {
      then = result.then
    } catch (error) {
      reject(promise, error)
    }

    if (isFunction(then)) {
      try {
        let resolvePromise = function (y) {
          resolve(promise, y)
        }
        let rejectPromise = function (r) {
          reject(promise, r)
        }
        then.call(result, resolvePromise, rejectPromise)
      } catch (error) {
        reject(promise, error)
      }

      return
    }
  }

  promise._state = fulfilled
  promise._value = result

  if (promise._tasks && promise._tasks.length) {
    let task = null
    while (task = promise._tasks.shift()) {
      handlePromise(promise, task)
    }
  }
}
複製程式碼

reject

reject將promise的狀態設定為rejected, 並以當前的promise的狀態,執行promise中通過then註冊的onRejected回撥。


function reject (promise, reason) {
  if (promise._state !== pending) {
    return
  }

  promise._state = rejected
  promise._value = reason

  let task
  while (task = promise._tasks.shift()) {
    handlePromise(promise, task)
  }
}
複製程式碼

handlePromise

handlePromise函式主要根據當前的promise的狀態, 以及內容(resolve或者reject的引數)。處理通過then註冊的回撥。並且會鏈式的呼叫,註冊在then返回的新promise的上的then的回撥


// 將回撥的結果,傳入第二個then中
fn().then().then()
複製程式碼

根據規範2.2.4, 以及規範給出的註解。當promise的狀態改變,onFulfilled, onRejected並不會立即執行,而且在本次的巨集任務完成後,才會執行onFulfilled或者onRejected。而setImmediate則是將程式碼push到微任務佇列中。在巨集任務中會清空微任務佇列。


function handlePromise (prevPromise, task) {
  // 需要在巨集任務完後的微任務佇列中執行
  setImmediate(() => {
    // nextPromise是then返回的promise
    const { onFulfilled, onRejected, promise: nextPromise } = task
    let callback = null

    let value = prevPromise._value
    let state = prevPromise._state

    if (state === fulfilled) {
      callback = onFulfilled
    } else if (state === rejected) {
      callback = onRejected
    }

    if (!callback) {
      // 如果在promise中沒有註冊callback
      if (state === fulfilled) {
        resolve(nextPromise, value)
      } else if (state === rejected) {
        reject(nextPromise, value)
      }
    } else {
      try {
        const result = callback(value)
        // 對then中返回promise處理
        // 將callback返回的結果,帶入到新的promise中
        resolve(nextPromise, result)
      } catch (error) {
        reject(nextPromise, error)
      }
    }
  })
}
複製程式碼

Promise.resolve & Promise.reject

Promise.resolve方法返回一個新的Promise物件,狀態為resolved。Promise.reject(reason)方法也會返回一個新的 Promise例項,該例項的狀態為rejected。

class Promise {

  // ...

  static resolve (result) {
    return new Promise((resolve) => { resolve(result) })
  }

  static reject (reason) {
    return new Promise((_, reject) => { reject(reason) })
  }
}
複製程式碼

Promise.all && Promise.race

Promise.all和Promise.race必須接受一個陣列為引數,陣列中為多個Promise的例項。Promise.all和Promise.race的使用我就不再這裡贅述了。

Promise.all會使用計數器,記錄Promise陣列中的所有Promise例項的狀態是否都變為fulfilled態,如果計數器的長度和陣列長度一致,我們則會將Promise.all的狀態設定為fulfilled態。


class Promise {
  static race (promises) {
    if (isArray(promises)) {
      let promisesLength = promises.length
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promisesLength; i++) {
          promises[i].then((result) => {
            resolve(result)
          }).catch((error) => {
            reject(error)
          })
        }
      })
    } else {
      throw new TypeError('The arguments must be arrays')
    }
  }

  static all (promises) {
    if (isArray(promises)) {
      let promisesLength = promises.length
      let counter = 0
      let resultList = []
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promisesLength; i++) {
          promises[i].then((result) => {
            counter += 1
            resultList.push(result)
            if (counter === promisesLength) {
              resolve(resultList)
            }
          }).catch((error) => {
            reject(error)
          })
        }
      })
    } else {
      throw new TypeError('The arguments must be arrays')
    }
  }
}
複製程式碼

其他

VueRouter原始碼分析

Preact原始碼分析

相關文章