await を書ける場所は決まっています。async を付け忘れた関数、コールバックの中、昔の書き方のファイル。それぞれの直し方と、直った後に起きやすい間違いまで載せています。
await を書ける場所は決まっています。
example.js
try {
new Function('return await Promise.resolve(1);');
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
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
async function load() {
return await Promise.resolve('読み込んだ');
}
console.log(await load());
出力書き換えて実行できます
読み込んだ
async を付けます。
example.js
const load = async () => await Promise.resolve('読み込んだ');
console.log(await load());
出力書き換えて実行できます
読み込んだ
example.js
const api = {
async load() {
return await Promise.resolve('メソッド');
},
};
class Client {
async load() {
return await Promise.resolve('クラス');
}
}
console.log(await api.load());
console.log(await new Client().load());
出力書き換えて実行できます
メソッド
クラス
アロー関数も同じです。async は矢印の前に書きます。
javascript
async function main() {
[1, 2].forEach(function (n) {
await save(n);
});
}
example.js
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const order = [];
[1, 2].forEach(async (n) => {
await sleep(1);
order.push(n);
});
order.push('forEach を抜けた');
await sleep(30);
console.log(order.join(' -> '));
出力書き換えて実行できます
forEach を抜けた -> 1 -> 2
オブジェクトのメソッドとクラスのメソッドは、名前の前に書きます。
example.js
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const order = [];
for (const n of [1, 2]) {
await sleep(1);
order.push(n);
}
order.push('for...of を抜けた');
console.log(order.join(' -> '));
出力書き換えて実行できます
1 -> 2 -> for...of を抜けた
example.js
const double = async (n) => n * 2;
console.log(await Promise.all([1, 2].map(double)));
出力書き換えて実行できます
[ 2, 4 ]
原因2: コールバックの中に書いた
外側に async があっても、届きません。
example.js
async function main() {
return await Promise.resolve('囲んだ');
}
main().then((value) => console.log(value));
await new Promise((resolve) => setTimeout(resolve, 10));
出力書き換えて実行できます
囲んだ
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
async function get() {
return { name: 'あ' };
}
const user = await get();
console.log(user.name);
出力書き換えて実行できます
あ
構文の誤りは消えますが、待ってくれません。
forEach() はコールバックが返した Promise を見ないからです。
for...of に替えてください。
example.js
async function fail() {
throw new Error('だめ');
}
try {
await fail();
} catch (error) {
console.log('catch:', error.message);
}
出力書き換えて実行できます
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
探しかたの順序
await を書いた行から、いちばん近い関数を探す
- その関数に
async が付いているか見る
- 付いていない → 付ける
- その関数がコールバックだった →
for...of に替えるか、Promise.all() にする
- 関数の外側なら、そのファイルがモジュールとして読まれているか確かめる
- 直ったら、
await の付け忘れが無いか見直す
このページの実行結果は Node 22.22.3 のものです。文言は処理系と版によって変わります。
まとめ
await はasync を付けた関数の中とモジュールの一番外側だけ
- 外側に
async があっても、コールバックの中には届かない
forEach() に async を付けても待ってくれない。for...of か Promise.all()
- 順番に待つなら
for...of、同時でよいなら map() と Promise.all()
- 直った後は
await の付け忘れに注意。undefined が出たらまずそこ