最簡實現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) 複製程式碼
本文將圍繞這個最核心的案例來講,這段程式碼的表現如下:
- 500ms後輸出1
- 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) }) }) 複製程式碼
注意這裡的命名:
- 我們把Promise構造函數返回的實例叫做
promise1
, - 在then的實現中,我們構造了一個新的promise返回,叫它
promise2
- 在用戶調用then方法的時候,用戶手動構造了一個promise用來做非同步的操作,叫它
user promise
那麼在then的實現中,self其實就指向promise1
而promise2
的excutor中,立刻執行了一個函數,它往promise1
的onResolvedCallback
數組中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
才會執行,這就實現了非同步的鏈式調用。
要點總結
一個核心的要點:
- 簡單情況
then1
函數是個同步函數,返回一個普通的值。then1
里傳入的函數,其實是被放到promise1
的回調數組裡,
// promise1 new Promise(resolve => { setTimeout(resolve, 1000) }) // then1 這裡傳入的函數 會被放到調用者promise的回調數組中 .then(res => { console.log(res) }) 複製程式碼
這樣的話,1秒後,promise1
被resolve了,是不是then1
里的函數就被執行了呢~
- 複雜情況 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+規範還是值得每一個合格的前端開發去閱讀的。
希望這篇文章可以對你有所幫助!