Promise原理以及實現(Promise/A+規範) 下

實現基礎的雛形(成功狀態resolve):

function oPromise(fn) {
  var value = null,
    callbacks = []  //callbacks爲數組, 由於可能同時有不少個回調
  this.then = function (fulfilled) {
    callbacks.push(fulfilled)
    return this //
  }
  function resolve(newValue) {
    value = newValue
    callbacks.forEach(function (callback) {
      callback(value)
    })
  }
  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之後閱讀, 由於與resolve原理同樣
  setTimeout(() => { //處理異步
    resolve('dz')
  }, 0)
}).then(res => {
  console.log(res)
})
複製代碼

大體邏輯:javascript

  1. new oPromise實例, 執行fn(), 傳入的是內部resolve函數, 由於傳入的回調有setTimeout定時器是一個異步, 因此resolve會被延遲執行
  2. 而後就是執行then方法, 把then包含的回調註冊(添加)到callbacks數組中, 由於返回了實例->this, 所以若是有序還有其餘then會一次被註冊到callbacks
  3. 而後執行被延時的setTimeout, 並執行了resolve, 循環執行數組, 而後把value賦值給回調

問題:java

  • Promise/A+規範要求回調須要經過異步方式執行, 雖然已經設置了setTimeout, 可是若是沒有設置的話, oPromise內部的回調執行任然是同步, 這樣會致使then方法尚未執行就執行了resolve, 此時callbacks裏仍是空的..

意思就是說, 實例化的時候傳入了執行函數, 這個函數內部並非全部時候都是異步的, 也有時候是同步代碼例如:數組

new oPromise(resolve => {
  let a = 1 + 1
  resolve(a)
}).then(res => console.log) // 2
複製代碼

這個時候全部代碼都是同步的所以會先執行回調, 而後再執行 then, 這樣的話 callbacks 數組先被執行, 而後最後添加回調, 這樣的意義何在呢...promise

所以, 將 resolve 延遲執行(也知足了 Promise/A+ 規範):bash

// resolve 函數
function resolve(newValue) {
  value = newValue
  setTimeout(() => {
    callbacks.forEach(function (callback) {
      callback(value)
    })
  }, 0)
}
複製代碼

加上狀態

Promises/A+規範規定, pending能夠轉化爲fulfilled或rejected而且只能轉化一次, 也就是說若是pending轉化到fulfilled狀態, 那麼就不能再轉化到rejected. 而且fulfilled和rejected狀態只能由pending轉化而來, 二者之間不能互相轉換
複製代碼

加上狀態後:異步

function oPromise(fn) {
  let value = null,
      state = 'pending'
      callbacks = []  //callbacks爲數組, 由於可能同時有不少個回調

  this.then = function (fulfilled) {
    if (state === 'pending') {
      callbacks.push(fulfilled)
    } else {
      fulfilled(value)
    }
    return this //返回實例
  }

  function resolve(newValue) {
    if (state !== 'pending') return
    setTimeout(() => {
      value = newValue
      state = 'fulfilled'
      callbacks.forEach(function (callback) {
        callback(value)
      })
    }, 0)
  }

  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之後閱讀, 由於與resolve原理同樣, 後續再加入
  setTimeout(() => { //處理異步
    resolve('dz')
  }, 0)
}).then(res => {
    console.log(res)
    return 'dz'
   })
複製代碼

resolve執行時將狀態變爲fulfilled, 若是回調是異步, 就會先執行 then, 此時的 state 爲pending, 就將 then 註冊的回調添加到callbacks中, 若是回調不是異步, 就會由於state爲 fulfilled 而當即執行函數

resolve 方法中第一條語句是判斷狀態是否爲 pending, 這條語句的緣由是: 一旦狀態被 resolve 或者 reject 改變後 就不能再改變了, 在後續 寫到 加入 reject 後 再詳細說明post

鏈式調用

每一次調用 fulfilled 回調 咱們都把返回值保存下來, 以讓下一個then中的回調(也就是下一個 fulfilled)得到返回值, 簡單改造一下ui

function oPromise(fn) {
  let value = null,
      state = 'pending'
      callbacks = [] //callbacks爲數組, 由於可能同時有不少個回調

  this.then = function (fulfilled) {
    if (state === 'pending') {
      callbacks.push(fulfilled)
    } else {
      fulfilled(value)
    }
    return this //返回實例
  }

  function resolve(newValue) {
    if (state !== 'pending') return
    setTimeout(() => {
      value = newValue // 這裏放在setTimeout才比較正確 否則不能鏈式調用
      state = 'fulfilled'
      callbacks.forEach(function (callback) {
        value = callback(value) // 保存上一個value: 關鍵
      })
    }, 0)
  }
  fn(resolve)
}

new oPromise((resolve) => { //省略了rejected, 方便之後閱讀, 由於與resolve原理同樣
  setTimeout(() => { //處理異步
    resolve('dz')
  }, 0)
}).then(res => {
    console.log(res)
    return 'dz'
  }).then(res => {
    console.log(res)
  })
複製代碼

雖然上面看似沒有問題, 但其實有很大的缺陷:this

若是then的執行回調中的代碼爲異步, 此時不能保證異步執行後 返回的值能被下一個then執行回調所接收. 不對, 是確定不能

所以, 解決異步並回調的根本方式就是回到最初的問題 --> Promise

若是then回調函數中的代碼有異步代碼, 就將異步代碼放置在 Promise中, 而後將返回值傳給下一個 then中的回調, 這樣便造成 層層鏈式, 多安逸...

鏈式Promise是指在當前promise達到fulfilled狀態後, 即開始進行下一個promise(後鄰promise)

所以就須要在then方法裏return一個Promise

function oPromise(fn) {
  let value = null,
      state = 'pending',
      callbacks = []  //callbacks爲數組, 由於可能同時有不少個回調

  this.then = function (fulfilled) {
    return new oPromise((resolve) => {
      handle({
        fulfilled: fulfilled || null,
        resolve // 將下一個promise的resolve和then的回調一塊兒添加到callbacks中, 爲了在本次執行resolve時, 將本次的返回值傳遞到下一個promise
      })
    })
  }

  function handle(callback) {
    if (state === 'pending') {
      callback.push(callback)
      return
    }

    if (!callback.fulfilled) { // then 沒有回調函數
      callback.resolve(value) // 就直接將value值傳給-> 下 -> 下 個promise(若是有的話)
      return
    }
    const ret = callback.fulfilled(value) // 接受上一個promise返回值
    callback.resolve(ret) // 傳入下一個promise... 有點暈, 要仔細想一想..
  }
  function resolve(newValue) { //若是then函數的回調爲一個新的promise, 須要作一些特殊處理
    if (state !== 'pending') return
    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
      const then = newValue.then // 獲取 promise的then
      if (typeof then === 'function') {
        then.call(newValue, resolve) // 添加then方法的回調, 把本次then回調的執行結果傳給下一個promise, 並執行
        return
      }
    }

    const fn = () => {
      value = newValue
      state = 'fulfilled'
      handleCb()
    }
    setTimeout(fn, 0)
  }

  function handleCb() {
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }
  fn(resolve)
}
複製代碼

整個過程的關鍵在於handle函數的執行, 它分擔了聯繫相鄰兩個promise的做用:

  1. 根據state來處理是添加回調仍是執行, 若是沒有回調就跳過本次promise執行再下一個then(promise)
  2. 若是是pending就是添加回調, 把下一個then產生promise中的resolve一塊兒添加
  3. 若是是fulfilled就是執行, 就會調用下一個promise的resolve, 並傳入本次回調產生的結果值

失敗處理 reject

在異步操做失敗時, 標記其狀態爲rejected, 並執行註冊的失敗回調
複製代碼
function oPromise(fn) {
  let value = null,
    state = 'pending',
    callbacks = []  //callbacks爲數組, 由於可能同時有不少個回調
  this.then = function (fulfilled, rejected) {
    return new oPromise((resolve, reject) => {
      handle({
        fulfilled: fulfilled || null,
        rejected: rejected || null,
        resolve,
        reject // 和resolve同樣, 將一下個promise的錯誤執行(reject)加入callbacks中
      })
    })
  }
  function handle(callback) {
    if (state === 'pending') {
      callbacks.push(callback)
      return
    }
    let cb = state === 'fulfilled' ? callback.fulfilled : callback.rejected
    let r = state === 'fulfilled' ? callback.resolve : callback.reject
    let ret = null
    if (cb === null) {
      cb(value)
      return
    }
    ret = cb(value)
    r(ret)
  }

  // 省略 resolve, 減小篇幅. function resolve(newValue) {...}

  function reject(reason) {
    if (state !== 'pending') return
    const fn = () => {
      if (state !== 'pending') return
      if (reason && (typeof reason === 'object' || typeof reason === 'function')) {
        const then = reason.then // 獲取 promise的then
        if (typeof then === 'function') {
          then.call(reason, resolve) // 添加then方法的回調, 把本次then回調的執行結果傳給下一個promise, 並執行
          return
        }
      }
      state = 'rejected'
      value = reason
      handleCb()
    }
    setTimeout(fn, 0)
  }
  function handleCb() { // 將公共部分處理提取出來
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }

  fn(resolve, reject)
}
複製代碼

加入了失敗狀態的處理以後, 大體原理仍是同樣, 只是在執行callbacks裏面的回調和執行下一個promise的處理(resolve或者reject)須要作一些判斷

這裏說一下 resolvereject 方法中第一個判斷語句的做用, 舉個栗子, 假如沒有判斷:

const promise = new oPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 100)
  setTimeout(() => {
    reject(2)
  }, 100)
})
複製代碼

上面代碼執行後 200ms 後 promise 的狀態爲 rejected 而不是 fulfilled, 原則上 一旦改變就不能再改變了

異常處理 catch

若是在執行過程當中發生代碼錯誤, 那麼就用try catch來捕獲錯誤, 並把錯誤轉成失敗狀態, 就是把promise的狀態設爲rejected狀態, 執行後續錯誤的回調

改造handle方法

function handle(callback) {
    if (this.state === 'pending') {
      callbacks.push(callback)
      return
    }
    const { fulfilled, rejected, resolve, reject } = callback
    const cb = this.state === 'fulfilled' ? fulfilled : rejected
    const next = this.state === 'fulfilled' ? resolve : reject
    if (!cb) { // 沒有 回調 就直接進入下一個promise
      next(value)
      return
    }

    try {
      const ret = cb(value)
      // ①
      resolve(ret) // 這裏始終是 resolve, 即時 state 是 rejected
    } catch (error) {
      callback.reject(error)
    }allback.reject(e) //錯誤 直接執行失敗狀態回調
  }
}
複製代碼

須要注意的就是, 上面 代碼 註釋 處 始終是 resolve 的緣由是 和 寫的這篇Promise 上 所說的 這一級 執行錯誤回調, 可是返回值仍然是下一級的 resolved 回調 接收並執行

異常處理能夠直接使用 catch 來接受

this.catch = rejected => {
  this.then(null, rejected) // 在 鏈式調用末尾再添加一個只有 rejected 的 then
}
複製代碼

這樣就實現了catch 功能, 若是前面then中都沒有 rejected 回調(由於值都爲 null) , 會直接將值傳給最後catch(也就是最後的then)

靜態方法 oPromise.resolve

Promise.resolve 的入參可能有如下幾種狀況:

  • 無參數 [直接返回一個resolved狀態的 Promise 對象]
  • 普通數據對象 [直接返回一個resolved狀態的 Promise 對象]
  • promise對象 [返回一個新的Promise對象而且這個對象的 resolve 方法 交給傳入的promise對象來執行]
// function oPromise(fn).then(...)
oPromise.resolve = function (value) {
  if (state !== 'pending') return
  if (value && (typeof value === 'object' && typeof value.then === 'function')) {
    const { then } = value
    return new oPromise(resolve => {
      then(resolve) // ②
    })
  } else if (value) {
    return new oPromise(resolve => resolve(value))
  } else {
    return new oPromise(resolve => resolve())
  }
}

oPromise.resolve(1).then(res => console.log(res)) // 1
複製代碼

上面代碼中 處的意思就是, 當傳入的參數(value) 是這樣的:

let value = new Promise(resolve => {
  resolve(1)
}).then(res => {
  return res + 1
})
Promise.resolve(value)
       .then(res => console.log(res)) // 2
複製代碼

雖然 value 已是一個 promise 實例了, 貌似直接返回(如上面代碼中的), 而後再被後續的 then 註冊是沒有問題的. 可是 value 是一個失敗狀態的 promise 實例呢:

let value = new Promise((resolve, reject) => {
  reject(1)
}).then(res => {
  return res + 1
}, err => {
  console.log(err)
  return err + 1
})
Promise.resolve(value).then(res => {
  console.log(res)
}, err => {
  console.log(err) // what?? 我是2???
})
複製代碼

若是像 處那樣直接返回, 後續的 then 實例中的回調是會被執行 rejected 回調的, 這樣就是說, 我 oPromise.resolve 竟然返回的是一個失敗狀態的 promise ??? Oh, No

靜態方法 oPromise.reject

Promise.rejectPromise.resolve 相似, 區別在於 Promise.reject 始終返回一個狀態的rejected的Promise實例, 而 Promise.resolve 的參數若是是一個 Promise 實例的話, 返回的是參數對應的 Promise 實例, 因此狀態不必定

oPromise.reject = function (value) {
  return new Promise(function(resolve, reject) {
    reject(value)
  })
}
複製代碼

靜態方法 oPromise.all

入參是一個 Promise 的實例數組, 而後註冊一個 then 方法, 而後是數組中的 Promise 實例的狀態都轉爲 fulfilled 以後則執行 then 方法. 這裏主要就是一個計數邏輯, 每當一個 Promise 的狀態變爲 fulfilled 以後就保存該實例返回的數據, 而後將計數減一, 當計數器變爲 0 時, 表明數組中全部 Promise 實例都執行完畢.

oPromise.all = function (arr) {
  let args = Array.prototype.slice.call(arr)
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([])
    let remaining = args.length
    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          let then = val.then
          if (typeof then === 'function') {
            then.call(val, function (val) { // 這裏若是傳入參數是 promise的話須要將結果傳入 args, 而不是 promise實例
              res(i, val) 
            }, reject)
            return
          }
        }
        args[i] = val
        if (--remaining === 0) {
          resolve(args)
        }
      } catch (ex) {
        reject(ex)
      }
    }
    for (let i = 0; i < args.length; i++) {
      res(i, args[i])
    }
  })
}
複製代碼

靜態方法 oPromise.race

有了 oPromise.all 的理解, oPromise.race 理解起來就更容易了. 它的入參也是一個 Promise 實例數組, 而後其 then 註冊的回調方法是數組中的某一個 Promise 的狀態變爲 fulfilled 的時候就執行. 由於 Promise 的狀態只能改變一次, 那麼咱們只須要把 Promise.race 中產生的 Promise 對象的 resolve 方法, 注入到數組中的每個 Promise 實例中的回調函數中便可.

oPromise.race = function (args) {
  return new oPromise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}
複製代碼

finally

無論Promise最後的狀態如何 都要執行一些最後的操做. 咱們把這些操做放到 finally 中 也就是說 finally 註冊的函數是與 Promise 的狀態無關的 不依賴 Promise 的執行結果

function oPromise(fn) {
  // ...
this.finally = function(done) {
  this.then(() => {
    done()
  }, () => {
    done()
  })
}
  // ...
}
複製代碼

之因此沒有吧 done 直接傳給 then 是由於 原版 Promisefinally 執行回調中並無傳入任何參數

偷偷地作了修改...

較完整代碼

function oPromise(fn) {
  this.state = 'pending'
  this.value = null
  let callbacks = []
  this.then = function (fulfilled, rejected) {
    return new oPromise((resolve, reject) => {
      handle({
        fulfilled: fulfilled,
        rejected: rejected,
        resolve,
        reject
      })
    })
  }
  this.catch = function (rejected) {
    this.then(null, rejected)
  }
  this.finally = function (done) {
    this.then(() => {
      done()
    })
  }
  const handle = (callback) => {
    if (this.state === 'pending') {
      callbacks.push(callback)
      return
    }
    const { fulfilled, rejected, resolve, reject } = callback
    const cb = this.state === 'fulfilled' ? fulfilled : rejected
    const next = this.state === 'fulfilled' ? resolve : reject
    if (!cb) {
      next(value)
      return
    }
    try {
      const ret = cb(value)
      resolve(ret)
    } catch (error) {
      callback.reject(error)
    }
  }
  const resolve = newValue => {
    if (state !== 'pending') return
    const fn = () => {
      if (this.state !== 'pending') return
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        const then = newValue.then
        if (typeof then === 'function') {
          then.call(newValue, resolve)
          return
        }
      }
      this.state = 'fulfilled'
      value = newValue
      handleCb()
    }
    setTimeout(fn, 0)
  }
  const reject = newValue => {
    if (state !== 'pending') return
    const fn = () => {
      if (this.state !== 'pending') return
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        const { then } = newValue
        if (typeof then === 'function') {
          then.call(newValue, reject)
          return
        }
      }
      value = newValue
      this.state = 'rejected'
      handleCb()
    }
    setTimeout(fn, 0)
  }
  const handleCb = _ => {
    while (callbacks.length) {
      const cb = callbacks.shift()
      handle(cb)
    }
  }
  fn(resolve, reject)
}
oPromise.resolve = function (value) {
  if (value && value instanceof oPromise) {
    return value
  } else if (value && (typeof value === 'object' && typeof value.then === 'function')) {
    const { then } = value
    return new oPromise(resolve => {
      then(resolve)
    })
  } else if (value) {
    return new oPromise(resolve => resolve(value))
  } else {
    return new oPromise(resolve => resolve())
  }
}
oPromise.race = function (args) {
  return new oPromise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}
oPromise.all = function (arr) {
  let args = Array.prototype.slice.call(arr)
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([])
    let remaining = args.length
    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          let then = val.then
          if (typeof then === 'function') {
            then.call(val, function (val) {
              res(i, val)
            }, reject)
            return
          }
        }
        args[i] = val
        if (--remaining === 0) {
          resolve(args)
        }
      } catch (ex) {
        reject(ex)
      }
    }
    for (let i = 0; i < args.length; i++) {
      res(i, args[i])
    }
  })
}
複製代碼

由於函數(function)中 this 指向問題, 我把全部的函數都換成了 箭頭函數, 由於能夠直接使用 this 嘿嘿嘿....

嘿嘿

原文出自 這裏, 大部分都是 copy 的, 嘿嘿. 可是加入了本身的理解, 嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿...

相關文章
相關標籤/搜索