@@ -684,4 +684,155 @@ async function logInOrder(urls) {
684
684
685
685
上面代码中,虽然` map ` 方法的参数是` async ` 函数,但它是并发执行的,因为只有` async ` 函数内部是继发执行,外部不受影响。后面的` for..of ` 循环内部使用了` await ` ,因此实现了按顺序输出。
686
686
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 ` 命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
687
838
0 commit comments