Skip to content

Commit c00389a

Browse files
committed
docs(async): add top-level await
1 parent 98e3bcb commit c00389a

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed

docs/async-iterator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function idMaker() {
4343
}
4444
```
4545

46-
上面代码中,`next()`方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议。也就是说,Iterator 协议里面`next()`方法只能包含同步操作。
46+
上面代码中,`next()`方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议,只要代码里面包含异步操作都不行。也就是说,Iterator 协议里面`next()`方法只能包含同步操作。
4747

4848
目前的解决方法是,将异步操作包装成 Thunk 函数或者 Promise 对象,即`next()`方法返回值的`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
4949

docs/async.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,4 +684,155 @@ async function logInOrder(urls) {
684684

685685
上面代码中,虽然`map`方法的参数是`async`函数,但它是并发执行的,因为只有`async`函数内部是继发执行,外部不受影响。后面的`for..of`循环内部使用了`await`,因此实现了按顺序输出。
686686

687+
## 顶层 await
688+
689+
根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
690+
691+
```javascript
692+
// 报错
693+
const data = await fetch('https://api.example.com');
694+
```
695+
696+
上面代码中,`await`命令独立使用,没有放在 async 函数里面,就会报错。
697+
698+
目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令。这个提案的目的,是借用`await`解决模块异步加载的问题。
699+
700+
```javascript
701+
// awaiting.js
702+
let output;
703+
async function main() {
704+
const dynamic = await import(someMission);
705+
const data = await fetch(url);
706+
output = someProcess(dynamic.default, data);
707+
}
708+
main();
709+
export { output };
710+
```
711+
712+
上面代码中,模块`awaiting.js`的输出值`output`,取决于异步操作。我们把异步操作包装在一个 async 函数里面,然后调用这个函数,只有等里面的异步操作都执行,变量`output`才会有值,否则就返回`undefined`
713+
714+
上面的代码也可以写成立即执行函数的形式。
715+
716+
```javascript
717+
// awaiting.js
718+
let output;
719+
(async function main() {
720+
const dynamic = await import(someMission);
721+
const data = await fetch(url);
722+
output = someProcess(dynamic.default, data);
723+
})();
724+
export { output };
725+
```
726+
727+
下面是加载这个模块的写法。
728+
729+
```javascript
730+
// usage.js
731+
import { output } from "./awaiting.js";
732+
733+
function outputPlusValue(value) { return output + value }
734+
735+
console.log(outputPlusValue(100));
736+
setTimeout(() => console.log(outputPlusValue(100), 1000);
737+
```
738+
739+
上面代码中,`outputPlusValue()`的执行结果,完全取决于执行的时间。如果`awaiting.js`里面的异步操作没执行完,加载进来的`output`的值就是`undefined`
740+
741+
目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操作有没有结束。
742+
743+
```javascript
744+
// awaiting.js
745+
let output;
746+
export default (async function main() {
747+
const dynamic = await import(someMission);
748+
const data = await fetch(url);
749+
output = someProcess(dynamic.default, data);
750+
})();
751+
export { output };
752+
```
753+
754+
上面代码中,`awaiting.js`除了输出`output`,还默认输出一个 Promise 对象(async 函数立即执行后,返回一个 Promise 对象),从这个对象判断异步操作是否结束。
755+
756+
下面是加载这个模块的新的写法。
757+
758+
```javascript
759+
// usage.js
760+
import promise, { output } from "./awaiting.js";
761+
762+
function outputPlusValue(value) { return output + value }
763+
764+
promise.then(() => {
765+
console.log(outputPlusValue(100));
766+
setTimeout(() => console.log(outputPlusValue(100), 1000);
767+
});
768+
```
769+
770+
上面代码中,将`awaiting.js`对象的输出,放在`promise.then()`里面,这样就能保证异步操作完成以后,才去读取`output`
771+
772+
这种写法比较麻烦,等于要求模块的使用者遵守一个额外的使用协议,按照特殊的方法使用这个模块。一旦你忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。而且,如果上面的`usage.js`又有对外的输出,等于这个依赖链的所有模块都要使用 Promise 加载。
773+
774+
顶层的`await`命令,就是为了解决这个问题。它保证只有异步操作完成,模块才会输出值。
775+
776+
```javascript
777+
// awaiting.js
778+
const dynamic = import(someMission);
779+
const data = fetch(url);
780+
export const output = someProcess((await dynamic).default, await data);
781+
```
782+
783+
上面代码中,两个异步操作在输出的时候,都加上了`await`命令。只有等到异步操作完成,这个模块才会输出值。
784+
785+
加载这个模块的写法如下。
786+
787+
```javascript
788+
// usage.js
789+
import { output } from "./awaiting.js";
790+
function outputPlusValue(value) { return output + value }
791+
792+
console.log(outputPlusValue(100));
793+
setTimeout(() => console.log(outputPlusValue(100), 1000);
794+
```
795+
796+
上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。
797+
798+
这时,模块的加载会等待依赖模块(上例是`awaiting.js`)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的`output`,不会因为加载时机的不同,而得到不一样的值。
799+
800+
下面是顶层`await`的一些使用场景。
801+
802+
```javascript
803+
// import() 方法加载
804+
const strings = await import(`/i18n/${navigator.language}`);
805+
806+
// 数据库操作
807+
const connection = await dbConnector();
808+
809+
// 依赖回滚
810+
let jQuery;
811+
try {
812+
jQuery = await import('https://cdn-a.com/jQuery');
813+
} catch {
814+
jQuery = await import('https://cdn-b.com/jQuery');
815+
}
816+
```
817+
818+
注意,如果加载多个包含顶层`await`命令的模块,加载命令是同步执行的。
819+
820+
```javascript
821+
// x.js
822+
console.log("X1");
823+
await new Promise(r => setTimeout(r, 1000));
824+
console.log("X2");
825+
826+
// y.js
827+
console.log("Y");
828+
829+
// z.js
830+
import "./x.js";
831+
import "./y.js";
832+
console.log("Z");
833+
```
834+
835+
上面代码有三个模块,最后的`z.js`加载`x.js``y.js`,打印结果是`X1``Y``X2``Z`。这说明,`z.js`并没有等待`x.js`加载完成,再去加载`y.js`
836+
837+
顶层的`await`命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
687838

0 commit comments

Comments
 (0)