SyntaxError: await is only valid in async functions

await を書ける場所は決まっています。async を付け忘れた関数、コールバックの中、昔の書き方のファイル。それぞれの直し方と、直った後に起きやすい間違いまで載せています。

実行して検証済み処理系Node 22.22.3対応ES2017難易度●●○○○目安8更新

await を書ける場所は決まっています。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
SyntaxError: await is only valid in async functions and the top level bodies of modules
javascript
function load() {
return await fetch('/api');
}

実行する前に弾かれます。 1行も動きません。

ここでは文言を見せるために new Function() を使っています。ふつうに書いた場合は、ファイルを読み込んだ時点で止まります。

書ける場所

場所 書けるか
async を付けた関数の中 書ける
モジュールの一番外側 書ける(ES2022)
async の無い関数の中 書けない
コールバックの中(そのコールバックに async が無い) 書けない
モジュールでないファイルの一番外側 書けない

原因1: async の付け忘れ

いちばん多い原因です。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
読み込んだ

async を付けます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
読み込んだ
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
メソッド クラス

アロー関数も同じです。async は矢印の前に書きます。

javascript
async function main() {
[1, 2].forEach(function (n) {
await save(n);
});
}
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
forEach を抜けた -> 1 -> 2

オブジェクトのメソッドとクラスのメソッドは、名前の前に書きます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
1 -> 2 -> for...of を抜けた
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 2, 4 ]

原因2: コールバックの中に書いた

外側に async があっても、届きません。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
囲んだ

await があるのは function (n) の中で、そこには async がありません。

ここで内側のコールバックに async を付けるのは、たいてい間違いです。

javascript
async function get() {
return { name: 'あ' };
}

const user = get();

console.log(user);
console.log(user.name);
Promise {} undefined
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8

構文の誤りは消えますが、待ってくれません。 forEach() はコールバックが返した Promise を見ないからです。

for...of に替えてください。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
catch: だめ

同時に走らせてよいなら map()Promise.all() です。

複数の非同期処理を同時に走らせる

原因3: モジュールとして読まれていない

文言の後半にある「the top level bodies of modules」がこれです。

モジュールなら、関数の外側でも await を書けます。

  • <script type="module"> で読み込む
  • 拡張子を .mjs にする、または package.json"type": "module" を書く

import / export が使えない環境なら、囲って呼びます。

このページの実行パネルは中身を非同期の関数として扱うので、一番外側の await がそのまま動きます。自分のファイルでも同じとは限りません。

直った後に起きやすい間違い

await を付け忘れる

こちらは構文エラーになりません。静かに壊れます。

async を付けた関数は、必ず Promise を返しますawait を付けないと、中身ではなく入れ物が返ります。

undefined が出たら、まず await の付け忘れを疑ってください。

失敗を受け止めない

await を付けていれば、ふつうの try / catch で受けられます。 付けていないと、try の外に抜けてしまいます。 → Error と try / catch / finally / throw

探しかたの順序

  1. await を書いた行から、いちばん近い関数を探す
  2. その関数に async が付いているか見る
    • 付いていない → 付ける
    • その関数がコールバックだったfor...of に替えるか、Promise.all() にする
  3. 関数の外側なら、そのファイルがモジュールとして読まれているか確かめる
  4. 直ったら、await の付け忘れが無いか見直す

このページの実行結果は Node 22.22.3 のものです。文言は処理系と版によって変わります。

まとめ

  • awaitasync を付けた関数の中モジュールの一番外側だけ
  • 外側に async があっても、コールバックの中には届かない
  • forEach()async を付けても待ってくれないfor...ofPromise.all()
  • 順番に待つなら for...of、同時でよいなら map()Promise.all()
  • 直った後は await の付け忘れに注意。undefined が出たらまずそこ

この記事の根拠

  1. Async Function Definitions — ECMAScript® 2026 Language Specificationtc39.es
  2. Await — ECMAScript® 2026 Language Specificationtc39.es
  3. await — MDNdeveloper.mozilla.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)