众所周知,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>