async await 續集: await 到底可以接什麼?僅僅是 Promise嗎?
眾所周知,async await 只是 Promise 的語法糖,但具體是什麼語法糖,我自己之前也沒細究。
昨天在研究 iOS JavaScriptCore 裡邊如何捕獲未處理的 Promise rejection,發現 jscore 本身並不提供任何介面,只能想其他辦法繞過去。
參考了 Egret Native 的實現,發現他們實現和自己的臆想也是吻合的,就是在 JS 側對 Promise 做覆蓋,或者叫 polyfill,這樣就能完整的掌控 Promise 實現和 reject 的處理了。當然,這樣做是有缺陷的,只能捕獲 Promise,但 async await 方法的報錯就無法捕獲了,除非 JS 側把這些都轉義為 ES5。
本文就是簡單探討一下 await 後邊可以跟什麼內容,這個和我的目標——“捕獲各種 Promise reject”是有關聯的。
1 await 接 Promise 例項
這個是最基礎用法,等待 Promise resolve 或 reject。resolve 後就同步執行,reject 就被 try catch 捕獲,或者不處理,由上層呼叫方法處理。
有個比較有趣的點是,無論是 js 側 polyfill 實現的 Promise,還是瀏覽器原生的 Promise,都可以接在 await 後,為什麼呢?
var b = new Promise((r,reject)=>reject('Promise reject result')); var basync = (async function(){ try{ await b; }catch(e){ console.log('error',e); } })();
2 await 接普通變數
這個是不推薦用法,但瀏覽器不會報錯,等同於 await 是多餘的。當然,我們自己不會直接寫出這樣的程式碼,往往是下游方法,可能某些分支情況下,直接返回了結果,而不是 Promise。正好瀏覽器這樣的相容處理,就有利於 await 後接一個有動態返回型別的 Function。
var c = {name: 'kenko'}; var casync = (async function(){ try{ await c; // await 不起作用,等於直接同步執行 console.log('ccc'); }catch(e){ console.log('error',e); } })();
3 await 接 Thenable 物件
這個才是真正答案。什麼是 Thenable,參考 MDN 對 await 的定義:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
Thenable 其實就是帶有 then 方法的物件,這個 then 方法應該接受兩個引數,一個是 resolve 回撥,一個是 reject 回撥,類似 Promise 的 then 方法。
所以,當然,Promise 是一種 Thenable 實現,無論瀏覽器原生的 Promise 還是 polyfill 的 Promise 都符合 Thenable 規範,所以剛才第一種情況下的疑問也解開了。await 後接 Promise 是最常見情況。
那麼 await 這個語法糖,實際具體做的事就有幾點:
1. 呼叫接的物件的 then 方法,分別傳入 resolve 和 reject 作為回撥。
2. 如果 thenable 物件 resolve 了,那麼 await 把 resolve 結果賦值給前邊變數(如果有),然後同步執行下一行程式碼;
3. 如果 thenable 物件 reject 了,那麼 await 把 reject 內容,throw 出去,所以緊接著如果有 try catch,這個 throw 內容就會被捕獲。如果沒有 try catch,這個 throw 會被整個 async 方法捕獲,作為對上層的 reject。
最後貼一個對比三種情況的 Demo:(polyfill 的 Promise,一個簡單的 Thenable,一個普通物件)
https://github.com/kenkozheng/HTML5_research/blob/master/Promise/await.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Await</title> </head> <body> <script>window.Promise = undefined;</script> <script src="https://cdn.bootcss.com/es6-promise/4.1.1/es6-promise.auto.js"></script> <script> function MyPromise(resolver){ resolver((r)=>{ this._result = r; this._state = 1; }, (r)=>{ this._result = r; this._state = 2; }); } var prototype = MyPromise.prototype; prototype._state = 0; prototype._result = undefined; prototype._resolveCallbackList = []; prototype._rejectCallbackList = []; prototype.then = function(onResolved, onRejected){ onResolved && this._resolveCallbackList.push(onResolved); this._rejectCallbackList.push(onRejected); this.trigger(); return this; }; prototype.catch = function(onRejected){ this._rejectCallbackList.push(onRejected); this.trigger(); return this; }; prototype.trigger = function(){ if(this._state === 1) { this._resolveCallbackList.forEach(func => func(this._result)); this._resolveCallbackList = []; } else if(this._state === 2) { this._rejectCallbackList.forEach(func => func(this._result)); this._rejectCallbackList = []; } } var aRejected = new MyPromise((resolve,reject)=>resolve('MyPromise reject result')); var aRejectedAsync = (async function(){ try{ await aRejected; //自定義的非嚴格A+ Promise實現,但是符合條件的thenable物件,await會等待 console.log('a resolved'); }catch(e){ console.log('error', e); } })(); var aResolved = new MyPromise((r,reject)=>reject('MyPromise reject result')); var aResolvedAsync = (async function(){ try{ await aResolved; //自定義的非嚴格A+ Promise實現,但是符合條件的thenable物件,await會等待 }catch(e){ console.log('error', e); } })(); var b = new Promise((r,reject)=>reject('Promise reject result')); var basync = (async function(){ try{ await b; // 使用的是polyfill版本Promise,實際就是一個function class例項 }catch(e){ console.log('error',e); } })(); var c = {name: 'kenko'}; var casync = (async function(){ try{ await c; // await 不起作用,等於直接同步執行 console.log('ccc'); }catch(e){ console.log('error',e); } })(); </script> </body> </html>
[email protected] https://github.com/kenkozheng 歡迎投簡歷給我,一線大廠工作機會