首頁 > 軟體

簡單版 Promise/A+,通過官方872個測試用例

2020-09-23 23:30:11

promise 標準

在實現 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 規範,所以我們在編寫 Promise 時也應當遵循這個規範,建議認真、仔細讀幾遍這個規範。最好是理解事件迴圈,這樣對於理解js中的非同步是怎麼回事非常重要。

https://promisesaplus.com/

基本使用

new Promise( function(resolve, reject) {...} /* executor */  );
new Promise((resolve, reject)=> { AjaxRequest.post({ url: 'url', data: {}, sueccess: ()=> { resolve(res) }, fail: (err)=> { reject(err) } }) }).then((res)=> { // do some }).then(value => { }).catch((err)=> { // do some })

promise 是處理非同步結果的一個物件,承若狀態改變時呼叫對應的回撥函數,resolve、reject用來改變promise 的狀態,then 繫結成功、失敗的回撥。

環境準備

安裝測試工具以及nodemon因為我們要在node環境偵錯自己寫的promise

// nodemon
npm install nodemon -D
// promise 測試工具
npm install promises-aplus-tests -D 

增加指令碼命令

"testPromise": "promises-aplus-tests myPromise/promise3.js",
 "dev": "nodemon ./myPromise/index.js -i "

各自的路徑改成自己的即可,這個在後面會用來測試。

基本架子

根據規範實現一個簡單的promise,功能如下

  1. promise的三種狀態(PENDING、FULFILLED、REJECTED)
  2. 狀態只能由 Pending 變為 Fulfilled 或由 Pending 變為 Rejected ,且狀態改變之後不會在發生變化,會一直保持這個狀態
  3. 繫結then的回撥
  4. 返回成功、失敗的值
  5. 一個promise 支援呼叫多次then
  6. 支援捕獲異常
/* 

基本架子
根據promise A+ 規範還要處理then鏈式呼叫以及返回值傳遞的問題,後續在promise2、promise3 處理

*/

const PENDING = 'PENDING',
      FULFILLED = 'FULFILLED',
      REJECTED = 'REJECTED';

class myPromise {
  constructor (executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
this.onResolveCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value // 釋出 this.onResolveCallbacks.forEach(fn => fn()) } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason // 釋出 this.onRejectedCallbacks.forEach(fn => fn()) } } try { // 執行傳進來的fn, 在給他提供改變狀態的fn executor(resolve, reject) } catch(e) { reject(e) } } // 訂閱回撥函數 then (onFulfilled, onRejected) { if (this.status = PENDING) { // 訂閱 this.onResolveCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(() => { onRejected(this.reason) }) } if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } } module.exports = myPromise

訂閱

   傳進來的fn是一個執行器,接受resolve、reject參數,通常我們在建構函式中需要呼叫某個介面,這是一個非同步的操作,執行完建構函式之後,在執行then(),這個時候的狀態還是pending,所以我們需要把then 繫結的回撥存起來,也可以理解為promise物件訂閱了這個回撥。

釋出

   在 resolve,reject函數中中我們改變了promise 物件的狀態,既然狀態改變了,那麼我們需要執行之前訂閱的回撥,所以在不同的狀態下執行對應的回撥即可。

流程

如上所示,例項化物件,執行建構函式,碰到非同步,掛起,然後執行then()方法,綁定了resolve、reject的回撥。如果非同步有了結果執行對應的業務邏輯,呼叫resolve、或者reject,改變對應的狀態,觸發我們繫結的回撥。

以上就是最基本的promise架子,但是還有promise 呼叫鏈沒有處理,下面繼續完善...

完善promise 呼叫鏈

promose 的精妙的地方就是這個呼叫鏈,首先then 函數會返回一個新的promise 物件,並且每一個promise 物件又有一個then 函數。驚不驚喜原理就是那麼簡單,回顧下then的一些特點

then 特點

  1. then 返回一個新的promise 物件
  2. then 繫結的回撥函數在非同步佇列中執行(evnet loop 事件迴圈)
  3. 通過return 來傳遞結果,跟fn一樣如果沒有return,預設會是 underfined
  4. 拋出異常執行繫結的失敗函數(最近的promise),如果沒有,則執行catch
  5. then中不管是不是非同步只要resolve、rejected 就會執行對應 onFulfilled、onRejected 函數
  6. then中返回promise狀態跟執行回撥的結果有關,如果沒有異常則是FULFILLED,就算沒有retun 也是FULFILLED,值是underfined,有異常就是REJECTED,接著走下個then 繫結的onFulfilled 、onRejected 函數

   根據上面的特點以及閱讀規範我們知道then()函數主要需要處理以下幾點

  • 返回一個新的promise
  • 值怎麼傳給then返回的那個promise
  • 狀態的改變

返回一個新的promise

因為promise 的鏈式呼叫涉及到狀態,所以then 中返回的promise 是一個新的promise

then(onFulfilled, onRejected) {
   let promise2 = new Promise((resolve, reject) => {
     // do ...
   })
   return promise2
 }

值的傳遞、狀態的改變

let p = new myPromise((resolve, rejected) => {
  // do ...
})
p.then(
  value => {
    return 1
  },
  reason => {}
  )
  .then(
    value => {
      return new Promise((resolve, rejected) => {
        resolve('joel')
      })
    },
    reason => {}
    )
  .then(
    value => {
      throw 'err: 出錯啦'
    },
    reason => {}
    )

then 返回的值可能是一個普通值、promise物件、function、error 等對於這部分規範文件也有詳細的說明

[[Resolve]](promise, x)

這個可以理解為promise 處理的過程,其中x是執行回撥的一個值,promise 是返回新的promise物件,完整程式碼如下

我們將這部分邏輯抽成一個獨立的函數 如下

// 處理then返回結果的流程
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<myPromise>'))
  }

  let called = false

  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then
      // 判斷是否是promise
      if (typeof then === 'function') {
        then.call(x, (y) => {
          // 如果 resolvePromise 以值 y 為參數被呼叫,則運行 [[Resolve]](promise, y)
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, (r) => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // 如果 x 不為物件或者函數,以 x 普通值執行回撥
    resolve(x)
  }
}

測試

promises-aplus-tests 這個工具我們必須實現一個靜態方法deferred,官方對這個方法的定義如下:

deferred: 返回一個包含{ promise, resolve, reject }的物件

promise 是一個處於pending狀態的promise

resolve(value) 用value解決上面那個promise

reject(reason) 用reason拒絕上面那個promise

新增如下程式碼

myPromise.defer = myPromise.deferred = function () {
  let deferred = {}

  deferred.promise = new myPromise((resolve, reject) => {
    deferred.resolve = resolve
    deferred.reject = reject
  })
  return deferred
}

在編輯執行我們前面加的命令即可

npm run testMyPromise

 

完善其他方法

  1. all
  2. allSettled
  3. any
  4. race
  5. catch
  6. finlly
npm run dev // 可以用來測試這些方法

源碼

源碼

參考

https://promisesaplus.com/

https://www.jianshu.com/p/4d266538f364

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

 

 


IT145.com E-mail:sddin#qq.com