背景: chrome 于 5 月 4 号发布 v9.1 版本 ,带来了 Top-level await 新特新,我们可以在模块顶级中使用 await,不需要在额外的加入 async。
什么是 Top-level await
如果我们试图在一个 async 函数外面使用 await 关键字,将会引起语法错误,例如:
1 | <script type="text/javascript"> |
会产生如下报错:
await is only valid in async functions and the top level bodies of modules
但是在 v9.1 我们可以在模块顶级中使用 await,不需要额外的加入 async。
1 | <script type="module">await Promise.resolve(console.log('执行'))</script> |
并且它有着如下特点:
- 顶层 await 在模块图的执行阶段发挥作用,此时所有的资源都已经获取并链接了,不存在资源被阻塞的风险;
- 顶层 await 只限于在 ES6 模块中使用,不支持普通脚本或者 CommonJS 模块
先看一个例子
在使用 ES6 模块化的时候,经常会遇到需要导入导出的场景:
1 | // a.js |
在这个例子中,我们在文件之间进行变量的导入导出。
通过分析代码我们可以发现第一次打印的都是 undefined 第二次打印得到的是 4。因为在 async 函数执行完毕之前,c.js 就已经访问了 b.js 导出的变量。name 怎么解决呢?
导出 IIFE promise
1 | // a.js |
我们将变量作为 async IIFE 的返回值返回。这样的话,c.js 只需简单地等待 promise 被 resolve,之后获取变量。
但是从静态分析、可测试性、工程学以及其它角度来讲,这种做法相比 ES2015 的模块化来说是一种显而易见的倒退。
Top-level await 怎么解决
我们仍然异步地初始化我们的导出,但是我们可以通过 Top-level await 来正常地使用 sum。
我们可以导入 b.js,而不需要知道它会异步初始化的导出:
1 | // a.js |
await promise 被 resolve 之前, c.js 中任意一条语句都不会执行。
Top-level await 是怎么工作的
JavaScript 会静态地确认哪些模块是异步的,这些模块导出的 Promise 都会放到 Promise.all() 中。其余的导入仍然照常处理,并且拒绝(reject)和同步的异常都会被转为异步函数。
Top-level await 还可以做些什么
资源初始化
1 | //connect() return a promise. |
动态加载模块
1 | const data = await import(`./file${v}.js`); |
资源加载备选方案
如果 CDN A 无法导入 Vue,那么会尝试从 CDN B 中导入。
1 | let Vue; |