天天看點

面試中的需求,手寫一個符合Promise/A+規範的Promise手寫 Promise

手寫 Promise

我們會通過手寫一個符合 Promise/A+ 規範的

Promise

來深入了解它,并且手寫

Promise

也是一道大廠常考題,在進入正題之前,推薦各位閱讀一下 Promise/A+ 規範,這樣才能更好地了解這個章節的代碼。

實作一個簡易版 Promise

在完成符合 Promise/A+ 規範的代碼之前,我們可以先來實作一個簡易版

Promise

,因為在面試中,如果你能實作出一個簡易版的

Promise

基本可以過關了。

那麼我們先來搭建建構函數的大體架構

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

function MyPromise(fn) {
  const that = this
  that.state = PENDING
  that.value = null
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  // 待完善 resolve 和 reject 函數
  // 待完善執行 fn 函數
}
           
  • 首先我們建立了三個常量用于表示狀态,對于經常使用的一些值都應該通過常量來管理,便于開發及後期維護
  • 在函數體内部首先建立了常量

    that

    ,因為代碼可能會異步執行,用于擷取正确的

    this

    對象
  • 一開始

    Promise

    的狀态應該是

    pending

  • value

    變量用于儲存

    resolve

    或者

    reject

    中傳入的值
  • resolvedCallbacks

    rejectedCallbacks

    用于儲存

    then

    中的回調,因為當執行完

    Promise

    時狀态可能還是等待中,這時候應該把

    then

    中的回調儲存起來用于狀态改變時使用

接下來我們來完善

resolve

reject

函數,添加在

MyPromise

函數體内部

function resolve(value) {
  if (that.state === PENDING) {
    that.state = RESOLVED
    that.value = value
    that.resolvedCallbacks.map(cb => cb(that.value))
  }
}

function reject(value) {
  if (that.state === PENDING) {
    that.state = REJECTED
    that.value = value
    that.rejectedCallbacks.map(cb => cb(that.value))
  }
}
           

這兩個函數代碼類似,就一起解析了

  • 首先兩個函數都得判斷目前狀态是否為等待中,因為規範規定隻有等待态才可以改變狀态
  • 将目前狀态更改為對應狀态,并且将傳入的值指派給

    value

  • 周遊回調數組并執行

完成以上兩個函數以後,我們就該實作如何執行

Promise

中傳入的函數了

try {
  fn(resolve, reject)
} catch (e) {
  reject(e)
}
           
  • 實作很簡單,執行傳入的參數并且将之前兩個函數當做參數傳進去
  • 要注意的是,可能執行函數過程中會遇到錯誤,需要捕獲錯誤并且執行

    reject

    函數

最後我們來實作較為複雜的

then

函數

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const that = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : r => {
          throw r
        }
  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  if (that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
}
           
  • 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
  • 當參數不是函數類型時,需要建立一個函數指派給對應的參數,同時也實作了透傳,比如如下代碼
    // 該代碼目前在簡單版中會報錯
    // 隻是作為一個透傳的例子
    Promise.resolve(4).then().then((value) => console.log(value))
               
  • 接下來就是一系列判斷狀态的邏輯,當狀态不是等待态時,就去執行相對應的函數。如果狀态是等待态的話,就往回調函數中

    push

    函數,比如如下代碼就會進入等待态的邏輯
    new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1)
      }, 0)
    }).then(value => {
      console.log(value)
    })
               

以上就是簡單版

Promise

實作,下面的是實作完整版

Promise

的解析,相信看完完整版的你,一定會對于

Promise

的了解更上一層樓。

實作一個符合 Promise/A+ 規範的 Promise

這小節代碼需要大家配合規範閱讀,因為大部分代碼都是根據規範去實作的。

我們先來改造一下

resolve

reject

函數

function resolve(value) {
  if (value instanceof MyPromise) {
    return value.then(resolve, reject)
  }
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
function reject(value) {
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
           
  • 對于

    resolve

    函數來說,首先需要判斷傳入的值是否為

    Promise

    類型
  • 為了保證函數執行順序,需要将兩個函數體代碼使用

    setTimeout

    包裹起來

接下來繼續改造

then

函數中的代碼,首先我們需要新增一個變量

promise2

,因為每個

then

函數都需要傳回一個新的

Promise

對象,該變量用于儲存新的傳回對象,然後我們先來改造判斷等待态的邏輯

if (that.state === PENDING) {
  return (promise2 = new MyPromise((resolve, reject) => {
    that.resolvedCallbacks.push(() => {
      try {
        const x = onFulfilled(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (r) {
        reject(r)
      }
    })

    that.rejectedCallbacks.push(() => {
      try {
        const x = onRejected(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (r) {
        reject(r)
      }
    })
  }))
}
           
  • 首先我們傳回了一個新的

    Promise

    對象,并在

    Promise

    中傳入了一個函數
  • 函數的基本邏輯還是和之前一樣,往回調數組中

    push

    函數
  • 同樣,在執行函數的過程中可能會遇到錯誤,是以使用了

    try...catch

    包裹
  • 規範規定,執行

    onFulfilled

    或者

    onRejected

    函數時會傳回一個

    x

    ,并且執行

    Promise

    解決過程,這是為了不同的

    Promise

    都可以相容使用,比如 JQuery 的

    Promise

    能相容 ES6 的

    Promise

接下來我們改造判斷執行态的邏輯

if (that.state === RESOLVED) {
  return (promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      try {
        const x = onFulfilled(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (reason) {
        reject(reason)
      }
    })
  }))
}
           
  • 其實大家可以發現這段代碼和判斷等待态的邏輯基本一緻,無非是傳入的函數的函數體需要異步執行,這也是規範規定的
  • 對于判斷拒絕态的邏輯這裡就不一一贅述了,留給大家自己完成這個作業

最後,當然也是最難的一部分,也就是實作相容多種

Promise

resolutionProcedure

函數

function resolutionProcedure(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Error'))
  }
}
           

首先規範規定了

x

不能與

promise2

相等,這樣會發生循環引用的問題,比如如下代碼

let p = new MyPromise((resolve, reject) => {
  resolve(1)
})
let p1 = p.then(value => {
  return p1
})
           

然後需要判斷

x

的類型

if (x instanceof MyPromise) {
    x.then(function(value) {
        resolutionProcedure(promise2, value, resolve, reject)
    }, reject)
}
           

這裡的代碼是完全按照規範實作的。如果

x

Promise

的話,需要判斷以下幾個情況:

  1. 如果

    x

    處于等待态,

    Promise

    需保持為等待态直至

    x

    被執行或拒絕
  2. 如果

    x

    處于其他狀态,則用相同的值處理

    Promise

當然以上這些是規範需要我們判斷的情況,實際上我們不判斷狀态也是可行的。

接下來我們繼續按照規範來實作剩餘的代碼

let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  try {
    let then = x.then
    if (typeof then === 'function') {
      then.call(
        x,
        y => {
          if (called) return
          called = true
          resolutionProcedure(promise2, y, resolve, reject)
        },
        e => {
          if (called) return
          called = true
          reject(e)
        }
      )
    } else {
      resolve(x)
    }
  } catch (e) {
    if (called) return
    called = true
    reject(e)
  }
} else {
  resolve(x)
}
           
  • 首先建立一個變量

    called

    用于判斷是否已經調用過函數
  • 然後判斷

    x

    是否為對象或者函數,如果都不是的話,将

    x

    傳入

    resolve

  • 如果

    x

    是對象或者函數的話,先把

    x.then

    指派給

    then

    ,然後判斷

    then

    的類型,如果不是函數類型的話,就将

    x

    傳入

    resolve

  • 如果

    then

    是函數類型的話,就将

    x

    作為函數的作用域

    this

    調用之,并且傳遞兩個回調函數作為參數,第一個參數叫做

    resolvePromise

    ,第二個參數叫做

    rejectPromise

    ,兩個回調函數都需要判斷是否已經執行過函數,然後進行相應的邏輯
  • 以上代碼在執行的過程中如果抛錯了,将錯誤傳入

    reject

    函數中

以上就是符合 Promise/A+ 規範的實作了,歡迎在評論中與我互動。

小結

這一章節我們分别實作了簡單版和符合 Promise/A+ 規範的

Promise

,前者已經足夠應付大部分面試的手寫題目,畢竟寫出一個符合規範的

Promise

在面試中不大現實。後者能讓你更加深入地了解

Promise

的運作原理,做技術的深挖者。

繼續閱讀