最簡實現Promise,支持異步鏈式調用(20行)

前言

在面試的時候,常常會有面試官讓你實現一個Promise,若是參照A+規範來實現的話,可能面到天黑都結束不了。前端

說到Promise,咱們首先想到的最核心的功能就是異步鏈式調用,本篇文章就帶你用20行代碼實現一個能夠異步鏈式調用的Promise。git

這個Promise的實現不考慮任何異常狀況,只考慮代碼最簡短,從而便於讀者理解核心的異步鏈式調用原理。github

代碼

先給代碼吧,真就20行。面試

function Promise(excutor) {
  var self = this
  self.onResolvedCallback = []
  function resolve(value) {
    setTimeout(() => {
      self.data = value
      self.onResolvedCallback.forEach(callback => callback(value))
    })
  }
  excutor(resolve.bind(self))
}
Promise.prototype.then = function(onResolved) {
  var self = this
  return new Promise(resolve => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data)
      if (result instanceof Promise) {
        result.then(resolve)
      } else {
        resolve(result)
      }
    })
  })
}
複製代碼

核心案例

new Promise(resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
})
  .then(res => {
    console.log(res)
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(2)
      }, 500)
    })
  })
  .then(console.log)
複製代碼

本文將圍繞這個最核心的案例來說,這段代碼的表現以下:數組

  1. 500ms後輸出1
  2. 500ms後輸出2

實現

構造函數

首先來實現Promise構造函數promise

function Promise(excutor) {
  var self = this
  self.onResolvedCallback = [] // Promise resolve時的回調函數集

  // 傳遞給Promise處理函數的resolve
  // 這裏直接往實例上掛個data 
  // 而後把onResolvedCallback數組裏的函數依次執行一遍就能夠
  function resolve(value) {
    // 注意promise的then函數須要異步執行 
    setTimeout(() => {
      self.data = value
      self.onResolvedCallback.forEach(callback => callback(value))
    })
  }

  // 執行用戶傳入的函數
  excutor(resolve.bind(self))
}
複製代碼

好,寫到這裏先回過頭來看案例frontend

const excutor = resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
}

new Promise(excutor)
複製代碼

分開來看,excutor就是用戶傳的函數,這個函數內部調用了resolve函數後,就會把promise實例上的onResolvedCallback執行一遍。異步

到此爲止咱們還不知道onResolvedCallback這個數組裏的函數是從哪裏來的,接着往下看。函數

then

這裏是最重要的then實現,鏈式調用全靠它:ui

Promise.prototype.then = function(onResolved) {
  // 保存上下文,哪一個promise調用的then,就指向哪一個promise。
  var self = this
 
  // 必定要返回一個新的promise
  // promise2
  return new Promise(resolve => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data)
      if (result instanceof Promise) {
        // resolve的權力被交給了user promise
        result.then(resolve)
      } else {
        resolve(result)
      }
    })
  })
}
複製代碼

再回到案例裏

var excutor = resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
}

var promise1 = new Promise(excutor)

promise1.then(res => {
  console.log(res)
  // user promise
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(2)
    }, 500)
  })
})
複製代碼

注意這裏的命名:

  1. 咱們把Promise構造函數返回的實例叫作promise1

  2. 在then的實現中,咱們構造了一個新的promise返回,叫它promise2

  3. 在用戶調用then方法的時候,用戶手動構造了一個promise用來作異步的操做,叫它user promise

那麼在then的實現中,self其實就指向promise1

promise2的excutor中,馬上執行了一個函數,它往promise1onResolvedCallback數組中push了一個函數,

那麼重點看這個push的函數,注意,這個函數在promise1被resolve了之後纔會執行。

self.onResolvedCallback.push(function() {
  // onResolved就對應then傳入的函數
  var result = onResolved(self.data)
  // 例子中的狀況 返回了一個promise3
  if (result instanceof Promise) {
    // 那麼直接把promise2的resolve決定權交給了user promise
    result.then(resolve)
  } else {
    resolve(result)
  }
})
複製代碼

若是用戶傳入給then的onResolved方法返回的是個promise,那麼這個user promise裏拿到的參數resolve,其實就指向了內部promise2的resolve,

因此這就能夠作到:user promise被resolve之後,then2函數纔會繼續執行,

new Promise(resolve => {
  setTimeout(() => {
    // resolve1
    resolve(1)
  }, 500)
})
  // then1
  .then(res => {
    console.log(res)
    // user promise
    return new Promise(resolve => {
      setTimeout(() => {
        // resolve2
        resolve(2)
      }, 500)
    })
  })
  // then2
  .then(console.log)
複製代碼

then1其實進入了promise1的回調數組裏,因此resolve1執行完畢後,then1纔會執行

then2其實進入了promise2的回調數組裏,又由於咱們剛剛知道,resolve2正是promise2的resolve方法,

因此resolve2執行完畢後, then2纔會執行,這就實現了異步的鏈式調用。

要點總結

一個核心的要點:

  1. 簡單狀況 then1函數是個同步函數,返回一個普通的值。 then1裏傳入的函數,實際上是被放到promise1的回調數組裏,
// promise1
new Promise(resolve => {
    setTimeout(resolve, 1000)
})
  // then1 這裏傳入的函數 會被放到調用者promise的回調數組中
.then(res => {
  console.log(res)
})
複製代碼

這樣的話,1秒後,promise1被resolve了,是否是then1裏的函數就被執行了呢~

  1. 複雜狀況 then函數返回了個promise 若是這個then函數裏返回了一個promise,那麼這個返回的promise內部的resolve,其實就指向
// 調用then的promise
new Promise(resolve => {
    setTimeout(resolve, 1000)
})
  // then2
.then(res => {
    // user promise
    return new Promise(resolve => {
        setTimeout(resolve, 1000)
    })
})
// then3
.then(res => {
    console.log(res)
})
複製代碼

then2會返回promise2(注意不是user promise,而是源碼內部返回的那個promise2),

then3傳入的函數會被放到promise2的回調數組裏。

因爲then2中用戶本身返回了一個user promise

因此promise2的resolve權力會被交給user promise

在1秒後,user promise被resolve了,那麼表明着promise2被reoslve了,那麼在promise2的回調數組裏會找到then3傳入的回調函數

它就被完美的執行了。

文章總結

以上代碼所有整理在了 Github倉庫

本文只是簡單實現一個能夠異步鏈式調用的promise,而真正的promise比它複雜不少不少,涉及到各類異常狀況、邊界狀況的處理。

promise A+規範仍是值得每個合格的前端開發去閱讀的。

但願這篇文章能夠對你有所幫助!

相關文章
相關標籤/搜索