在面試的時候,常常會有面試官讓你實現一個 Promise,若是參照 A+規範來實現的話,可能面到天黑都結束不了。前端
說到 Promise,咱們首先想到的最核心的功能就是異步鏈式調用,本篇文章就帶你用 20 行代碼實現一個能夠異步鏈式調用的 Promise。面試
這個 Promise 的實現不考慮任何異常狀況,只考慮代碼最簡短,從而便於讀者理解核心的異步鏈式調用原理。數組
先給代碼吧,真就 20 行。promise
function Promise(fn) { this.cbs = []; const resolve = (value) => { setTimeout(() => { this.data = value; this.cbs.forEach((cb) => cb(value)); }); } fn(resolve); } Promise.prototype.then = function (onResolved) { return new Promise((resolve) => { this.cbs.push(() => { const res = onResolved(this.data); if (res instanceof Promise) { res.then(resolve); } else { resolve(res); } }); }); }; 複製代碼
new Promise((resolve) => { setTimeout(() => { resolve(1); }, 500); }) .then((res) => { console.log(res); return new Promise((resolve) => { setTimeout(() => { resolve(2); }, 500); }); }) .then(console.log); 複製代碼
本文將圍繞這個最核心的案例來說,這段代碼的表現以下:markdown
首先來實現 Promise 構造函數異步
function Promise(fn) { // Promise resolve時的回調函數集 this.cbs = []; // 傳遞給Promise處理函數的resolve // 這裏直接往實例上掛個data // 而後把onResolvedCallback數組裏的函數依次執行一遍就能夠 const resolve = (value) => { // 注意promise的then函數須要異步執行 setTimeout(() => { this.data = value; this.cbs.forEach((cb) => cb(value)); }); } // 執行用戶傳入的函數 // 而且把resolve方法交給用戶執行 fn(resolve); } 複製代碼
好,寫到這裏先回過頭來看案例函數
const fn = (resolve) => { setTimeout(() => { resolve(1); }, 500); }; new Promise(fn); 複製代碼
分開來看,fn
就是用戶傳的函數,這個函數內部調用了 resolve
函數後,就會把 promise
實例上的 cbs
所有執行一遍。this
到此爲止咱們還不知道 cbs
這個數組裏的函數是從哪裏來的,接着往下看。spa
這裏是最重要的 then 實現,鏈式調用全靠它:prototype
Promise.prototype.then = function (onResolved) { // 這裏叫作promise2 return new Promise((resolve) => { this.cbs.push(() => { const res = onResolved(this.data); if (res instanceof Promise) { // resolve的權力被交給了user promise res.then(resolve); } else { // 若是是普通值 就直接resolve // 依次執行cbs裏的函數 而且把值傳遞給cbs resolve(res); } }); }); }; 複製代碼
再回到案例裏
const fn = (resolve) => { setTimeout(() => { resolve(1); }, 500); }; const promise1 = new Promise(fn); promise1.then((res) => { console.log(res); // user promise return new Promise((resolve) => { setTimeout(() => { resolve(2); }, 500); }); }); 複製代碼
注意這裏的命名:
咱們把 new Promise
返回的實例叫作promise1
在 Promise.prototype.then
的實現中,咱們構造了一個新的 promise 返回,叫它promise2
在用戶調用 then
方法的時候,用戶手動構造了一個 promise 而且返回,用來作異步的操做,叫它user promise
那麼在 then
的實現中,內部的 this 其實就指向promise1
而promise2
的傳入的fn
函數執行了一個 this.cbs.push()
,實際上是往 promise1
的cbs
數組中 push 了一個函數,等待後續執行。
Promise.prototype.then = function (onResolved) { // 這裏叫作promise2 return new Promise((resolve) => { // 這裏的this實際上是promise1 this.cbs.push(() => {}); }); }; 複製代碼
那麼重點看這個 push 的函數,注意,這個函數在 promise1
被 resolve 了之後纔會執行。
// promise2 return new Promise((resolve) => { this.cbs.push(() => { // onResolved就對應then傳入的函數 const res = onResolved(this.data) // 例子中的狀況 用戶本身返回了一個user promise if (res instanceof Promise) { // user promise的狀況 // 用戶會本身決定什麼時候resolve promise2 // 只有promise2被resolve之後 // then下面的鏈式調用函數纔會繼續執行 res.then(resolve) } else { resolve(res) } }) }) 複製代碼
若是用戶傳入給 then 的 onResolved 方法返回的是個 user promise
,那麼這個user promise
裏用戶會本身去在合適的時機 resolve promise2
,那麼進而這裏的 res.then(resolve)
中的 resolve 就會被執行:
if (res instanceof Promise) { res.then(resolve) } 複製代碼
結合下面這個例子來看:
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
這一整塊其實返回的是 promise2
,那麼 then2
其實本質上是 promise2.then(console.log)
,
也就是說 then2
註冊的回調函數,其實進入了promise2
的 cbs
回調數組裏,又由於咱們剛剛知道,resolve2
調用了以後,user promise
會被 resolve,進而觸發 promise2
被 resolve,進而 promise2
裏的 cbs
數組被依次觸發。
這樣就實現了用戶本身寫的 resolve2
執行完畢後,then2
裏的邏輯纔會繼續執行,也就是異步鏈式調用。
本文只是簡單實現一個能夠異步鏈式調用的 promise,而真正的 promise 比它複雜不少不少,涉及到各類異常狀況、邊界狀況的處理。
promise A+規範仍是值得每個合格的前端開發去閱讀的。
但願這篇文章能夠對你有所幫助!