一、Top-level Await
在ES2017中引入了 async 函数和 await 关键字,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内部使用。尝试在异步函数之外使用 await 就会报错:SyntaxError - SyntaxError: await is only valid in async function。
顶层 await 允许我们在 async 函数外面使用 await 关键字,目前提案正处于第 4 阶段,并且已经在三个主要的浏览器的 JavaScript 引擎中实现,模块系统会协调所有的异步 promise。
顶层 await 允许模块充当大型异步函数,通过顶层 await,这些ECMAScript模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行。下面来看一个简单的例子。
由于 await 仅在 async 函数中可用,因此模块可以通过将代码包装在 async 函数中来在代码中包含 await:
// a.js
import fetch from "node-fetch";
let users;
export const fetchUsers = async () => {
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
users = resp.json();
}
fetchUsers();
export { users };
// usingAwait.js
import {users} from './a.js';
console.log('users: ', users);
console.log('usingAwait module');
我们还可以立即调用顶层async函数(IIAFE):
import fetch from "node-fetch";
(async () => {
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
users = resp.json();
})();
export { users };
这样会有一个缺点,直接导入的 users 是 undefined,需要在异步执行完成之后才能访问它:
// usingAwait.js
import {users} from './a.js';
console.log('users:', users); // undefined
setTimeout(() => {
console.log('users:', users);
}, 100);
console.log('usingAwait module');
当然,这种方法并不安全,因为如果异步函数执行花费的时间超过100毫秒, 它就不会起作用了,users 仍然是 undefined。
另一个方法是导出一个 promise,让导入模块知道数据已经准备好了:
//a.js
import fetch from "node-fetch";
export default (async () => {
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
users = resp.json();
})();
export { users };
//usingAwait.js
import promise, {users} from './a.js';
promise.then(() => {
console.log('usingAwait module');
setTimeout(() => console.log('users:', users), 100);
});
虽然这种方法似乎是给出了预期的结果,但是有一定的局限性:导入模块必须了解这种模式才能正确使用它。
而顶层await就可以消除这些缺点:
// a.js
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const users = resp.json();
export { users};
// usingAwait.js
import {users} from './a.mjs';
console.log(users);
console.log('usingAwait module');
顶级 await 在以下场景中将非常有用:
- 动态加载模块
const strings = await import(`/i18n/${navigator.language}`);
- 资源初始化
const connection = await dbConnector();
- 依赖回退
let translations;
try {
translations = await import('https://app.fr.json');
} catch {
translations = await import('https://fallback.en.json');
}
目前,在这些地方已经支持 Top-level await: