mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 18:32:22 +00:00
docs(async): add top-level await
This commit is contained in:
parent
98e3bcb2df
commit
c00389ae77
@ -43,7 +43,7 @@ function idMaker() {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`next()`方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议。也就是说,Iterator 协议里面`next()`方法只能包含同步操作。
|
||||
上面代码中,`next()`方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议,只要代码里面包含异步操作都不行。也就是说,Iterator 协议里面`next()`方法只能包含同步操作。
|
||||
|
||||
目前的解决方法是,将异步操作包装成 Thunk 函数或者 Promise 对象,即`next()`方法返回值的`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
|
||||
|
||||
|
151
docs/async.md
151
docs/async.md
@ -684,4 +684,155 @@ async function logInOrder(urls) {
|
||||
|
||||
上面代码中,虽然`map`方法的参数是`async`函数,但它是并发执行的,因为只有`async`函数内部是继发执行,外部不受影响。后面的`for..of`循环内部使用了`await`,因此实现了按顺序输出。
|
||||
|
||||
## 顶层 await
|
||||
|
||||
根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
|
||||
|
||||
```javascript
|
||||
// 报错
|
||||
const data = await fetch('https://api.example.com');
|
||||
```
|
||||
|
||||
上面代码中,`await`命令独立使用,没有放在 async 函数里面,就会报错。
|
||||
|
||||
目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令。这个提案的目的,是借用`await`解决模块异步加载的问题。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
let output;
|
||||
async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
}
|
||||
main();
|
||||
export { output };
|
||||
```
|
||||
|
||||
上面代码中,模块`awaiting.js`的输出值`output`,取决于异步操作。我们把异步操作包装在一个 async 函数里面,然后调用这个函数,只有等里面的异步操作都执行,变量`output`才会有值,否则就返回`undefined`。
|
||||
|
||||
上面的代码也可以写成立即执行函数的形式。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
let output;
|
||||
(async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
})();
|
||||
export { output };
|
||||
```
|
||||
|
||||
下面是加载这个模块的写法。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import { output } from "./awaiting.js";
|
||||
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
```
|
||||
|
||||
上面代码中,`outputPlusValue()`的执行结果,完全取决于执行的时间。如果`awaiting.js`里面的异步操作没执行完,加载进来的`output`的值就是`undefined`。
|
||||
|
||||
目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操作有没有结束。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
let output;
|
||||
export default (async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
})();
|
||||
export { output };
|
||||
```
|
||||
|
||||
上面代码中,`awaiting.js`除了输出`output`,还默认输出一个 Promise 对象(async 函数立即执行后,返回一个 Promise 对象),从这个对象判断异步操作是否结束。
|
||||
|
||||
下面是加载这个模块的新的写法。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import promise, { output } from "./awaiting.js";
|
||||
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
promise.then(() => {
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,将`awaiting.js`对象的输出,放在`promise.then()`里面,这样就能保证异步操作完成以后,才去读取`output`。
|
||||
|
||||
这种写法比较麻烦,等于要求模块的使用者遵守一个额外的使用协议,按照特殊的方法使用这个模块。一旦你忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。而且,如果上面的`usage.js`又有对外的输出,等于这个依赖链的所有模块都要使用 Promise 加载。
|
||||
|
||||
顶层的`await`命令,就是为了解决这个问题。它保证只有异步操作完成,模块才会输出值。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
const dynamic = import(someMission);
|
||||
const data = fetch(url);
|
||||
export const output = someProcess((await dynamic).default, await data);
|
||||
```
|
||||
|
||||
上面代码中,两个异步操作在输出的时候,都加上了`await`命令。只有等到异步操作完成,这个模块才会输出值。
|
||||
|
||||
加载这个模块的写法如下。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import { output } from "./awaiting.js";
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
```
|
||||
|
||||
上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。
|
||||
|
||||
这时,模块的加载会等待依赖模块(上例是`awaiting.js`)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的`output`,不会因为加载时机的不同,而得到不一样的值。
|
||||
|
||||
下面是顶层`await`的一些使用场景。
|
||||
|
||||
```javascript
|
||||
// import() 方法加载
|
||||
const strings = await import(`/i18n/${navigator.language}`);
|
||||
|
||||
// 数据库操作
|
||||
const connection = await dbConnector();
|
||||
|
||||
// 依赖回滚
|
||||
let jQuery;
|
||||
try {
|
||||
jQuery = await import('https://cdn-a.com/jQuery');
|
||||
} catch {
|
||||
jQuery = await import('https://cdn-b.com/jQuery');
|
||||
}
|
||||
```
|
||||
|
||||
注意,如果加载多个包含顶层`await`命令的模块,加载命令是同步执行的。
|
||||
|
||||
```javascript
|
||||
// x.js
|
||||
console.log("X1");
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log("X2");
|
||||
|
||||
// y.js
|
||||
console.log("Y");
|
||||
|
||||
// z.js
|
||||
import "./x.js";
|
||||
import "./y.js";
|
||||
console.log("Z");
|
||||
```
|
||||
|
||||
上面代码有三个模块,最后的`z.js`加载`x.js`和`y.js`,打印结果是`X1`、`Y`、`X2`、`Z`。这说明,`z.js`并没有等待`x.js`加载完成,再去加载`y.js`。
|
||||
|
||||
顶层的`await`命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user