最簡實現Promise,支援非同步鏈式調用(20行)

  • 2020 年 4 月 11 日
  • 筆記

前言

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

說到Promise,我們首先想到的最核心的功能就是非同步鏈式調用,本篇文章就帶你用20行程式碼實現一個可以非同步鏈式調用的Promise。

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

程式碼

先給程式碼吧,真就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構造函數

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))  }  複製程式碼

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

const excutor = resolve => {    setTimeout(() => {      resolve(1)    }, 500)  }    new Promise(excutor)  複製程式碼

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

到此為止我們還不知道onResolvedCallback這個數組裡的函數是從哪裡來的,接著往下看。

then

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

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+規範還是值得每一個合格的前端開發去閱讀的。

希望這篇文章可以對你有所幫助!