トップレベルawait(Top level await)について改めて自分の中で整理することにした。
これはその際の備忘録的なものとなる。
目次
- 目次
- Top level awaitとは?
- Top level awaitをブラウザで試すシンプルな方法
- type="module"のあるファイルと、ないファイルが混在している場合
- CodeSandbox上だとtop level awaitは有効になっていない?
- ReactでTop level awaitを試す(webpack使用)
- Node.jsでTop level awaitを試す
- DenoでTop level awaitを試す
Top level awaitとは?
そもそもTop level awaitとは?というところだが、これはawaitキーワードを非同期関数(async function)の外で使用できるようにするものである。
いままではasync 関数の外で await を使おうとすると SyntaxError が発生した。
await Promise.resolve(console.log('Hello World')) // これは構文エラー(Syntax error)となる
そのため開発者側は上記のようにawait関数を利用するための手段として、すぐに呼び出される非同期関数(async function)を定義していた。
(async function () { await Promise.resolve(console.log('Hello World')); }());
だが、Top level awaitが利用できる環境であれば、最初に書いた下記の書き方でも問題なく動作する。
await Promise.resolve(console.log('Hello World')) // Hello World
上は単純な例だが、例えば下記のようにCDN経由でjQueryを読み込むなども行える。
const jQuery = await import('https://code.jquery.com/jquery-3.6.0.slim.min.js') console.log(jQuery) // Module {Symbol(Symbol.toStringTag): 'Module'}
try~catch
も可能なため、下記のようなコードも書ける
let aModule; try { aModule = await import('http://example.com'); } catch { aModule = await import('http://example.net'); }
Top level awaitをブラウザで試すシンプルな方法
Chromeがtop level awaitに対応した際に、開発者ツールのコンソール上で await
が使えるようになったので便利になった、というツイートを見かけた気がする。
例えば下記のようなコードをChromeの開発者ツールのconsole上で書くことができる。
const response = await fetch(url); response.status // => 200
私自身、開発者ツール上であれこれデバッグしたいときにawait周りで無駄に思考を張り巡らせていた経験があるので、シンプルに上のように書けるのはとても便利で感動した記憶がある。
さて、では実際にファイル上にtop level awaitを書きたい場合、どのように書けばよいか?
実際に読み込まれたJSファイル上でTop level awaitを行う場合は type="module"をつけて呼び出す
一応、今回の動作環境となるChromeのversionを先にメモしておく。
Google Chrome: 94.0.4606.71 (Official Build) (arm64)
まずはTop level awaitを定義したJSファイルを用意する( src/index.js
)
const a = await Promise.resolve("<h1>hello</h1>"); document.getElementById("app").innerHTML = a;
そして次にHTMLファイルを用意する。
<!DOCTYPE html> <html> <head> <title>Top level await sample (Vanilla JS)</title> <meta charset="UTF-8" /> </head> <body> <div id="app"></div> <script type="module" src="src/index.js"></script> </body> </html>
type="module"
と書かないと、src/index.js
のTop level awaitは有効にならない。
これでローカルサーバを経由してアクセスすることで想定した結果が得られる。
なお、ファイルを直接ブラウザで開くやり方の場合、下記のようなエラーが表示される。
Access to script at {file path} from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
type="module"をつけない場合はsyntax errorとなる
なお、下記のように type="module"
をつけない場合は、従来の通り、構文エラーとなる。
<!DOCTYPE html> <html> <head> <title>Top level await sample (Vanilla JS)</title> <meta charset="UTF-8" /> </head> <body> <div id="app"></div> <script src="src/index.js"></script> </body> </html>
この場合、下記のようなエラーがコンソール上に出力されるはず。
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
Top level awaitをVanilla JSで試す際の最もシンプルなサンプル
勿論ファイル経由で呼び出さず、下記のように書くこともできる。
たぶん、これが一番シンプルなTop level awaitの呼び出しサンプルかと思います。
<!DOCTYPE html> <html> <head> <title>Top level await sample (Vanilla JS)</title> <meta charset="UTF-8" /> </head> <body> <div id="app"></div> <script type="module"> const a = await Promise.resolve("hello"); document.getElementById("app").innerHTML = a; </script> </body> </html>
type="module"のあるファイルと、ないファイルが混在している場合
次にtype="module"
があるファイルと、ないファイルが混在している場合だが、type="module"
がついているファイルは最後に実行されることになる。
よって、awaitしている間にHTMLのパース自体がブロックされるということはない。
サンプルを下に上げる。
<!DOCTYPE html> <html> <head> <title>Top level await sample (Vanilla JS)</title> <meta charset="UTF-8" /> </head> <body> <div id="app"></div> <script type="module" src="src/index.js"></script> <script src="src/index2.js"></script> </body> </html>
console.log('call index.js');
console.log('call index2.js');
このようなファイル構成の場合、Chromeの開発者ツールからコンソールを確認するとログは下記のようになる。
call index2.js call index.js
CodeSandbox上だとtop level awaitは有効になっていない?
ちなみに上に書いたようなコードをCodeSandbox上で試そうとしても、エラーになるようだった。
これは単にCodeSandbox環境だとTop level awaitに対応していないだけだろうか?
ReactでTop level awaitを試す(webpack使用)
これについてはセットアップなどを記載していくと長くなりそうだったので別ポストに書いた。
Top level awaitを試すための方法をサクッと書いてしまうと、webpackのv5を用いて、webpack configに下記の記述を追加することで対応できる。
experiments: { topLevelAwait: true, },
Rollupの場合は experimentalTopLevelAwait
というオプションを利用するようだが、こちらについては試していません。
Node.jsでTop level awaitを試す
Node.js環境では 14.8.0
以降、標準にTop level awaitが組み込まれている。
node/CHANGELOG_V14.md at master · nodejs/node · GitHub
例えば下記のようなコードを書く。
(ちなみに今の環境内のNode.jsのversionを見ると、 v14.17.1
だったので併せて明記しておく)
const text = await Promise.resolve('Hello World'); console.log(text);
これを通常通りに実行すると、下記のようなエラーが表示される。
const text = await Promise.resolve('Hello World'); ^^^^^ SyntaxError: await is only valid in async function
Node.jsから実行する場合はpackage.json
にtype: module
を追加する。
{ ・ "type": "module", ・ ・ }
もしくは、ファイルの拡張子を mjs
にすることで実行可能となる。
DenoでTop level awaitを試す
確か記憶だとDenoは最初からTop level awaitが使えるようになっていた気がする。
自分の環境のDenoは最新から比べるとやや古くなっていた。せっかくなのでupgradeする。
denoでは下記のコマンドで最新にupgradeできるのでかんたん。
deno upgrade
というわけで、動作させるバージョンは 1.14.3
。
下記のようなコードを書く。
(といっても先程と同じコード)
// main.ts const text = await Promise.resolve("Hello World"); console.log(text);
そして下記のコマンドで実行する。
deno run main.ts
これで問題なく実行できる。