この記事で解決すること
「async/awaitって何?なんで必要なの?」
非同期処理の概念から、async/awaitの使い方まで段階的に解説します。
そもそも非同期処理とは
JavaScriptは基本的に「上から順番に実行」されます。でも、時間がかかる処理(API通信、ファイル読み込みなど)を待っている間、画面が固まったら困りますよね。
同期処理(待つ):
料理を注文 → 料理が来るまでボーッと待つ → 食べる
非同期処理(待たない):
料理を注文 → 待ってる間にスマホを見る → 料理が来たら食べる
非同期処理は「待っている間に他のことをする」仕組みです。Webアプリではfetch APIを使ったサーバーとの通信が代表的な非同期処理です。
歴史: なぜasync/awaitが生まれたか
時代1: コールバック(地獄)
// データを取得して、加工して、保存する
getData(function(data) {
processData(data, function(processed) {
saveData(processed, function(result) {
console.log(result);
// さらにネストが深くなる...
});
});
});
ネストが深くなり、読みにくい。これが「コールバック地獄」です。
時代2: Promise(チェーン)
getData()
.then(data => processData(data))
.then(processed => saveData(processed))
.then(result => console.log(result))
.catch(error => console.error(error));
だいぶ読みやすくなりました。でも .then() のチェーンが長くなると、まだ複雑です。
時代3: async/await(現在)
async function main() {
try {
const data = await getData();
const processed = await processData(data);
const result = await saveData(processed);
console.log(result);
} catch (error) {
console.error(error);
}
}
普通の同期処理のように読める!これがasync/awaitの最大のメリットです。JavaScriptの型安全性を高めたい場合は、TypeScriptを始めるべき理由もあわせて読んでみてください。
async/awaitの基本ルール
ルール1: awaitはasync関数の中でしか使えない
// NG: asyncがない関数内でawaitは使えない
function fetchUser() {
const response = await fetch('/api/user'); // エラー!
}
// OK: async を付ける
async function fetchUser() {
const response = await fetch('/api/user'); // OK!
}
ルール2: awaitは Promiseの完了を待つ
async function example() {
console.log('1: 開始');
// fetchはPromiseを返す。awaitで完了を待つ
const response = await fetch('https://api.example.com/data');
console.log('2: データ取得完了');
const data = await response.json();
console.log('3: JSON変換完了', data);
}
実行順序は必ず 1 → 2 → 3 になります。
ルール3: async関数は必ずPromiseを返す
async function greet() {
return 'こんにちは';
}
// 実際にはPromiseが返される
greet().then(message => console.log(message)); // こんにちは
実践例
例1: APIからデータを取得する
async function getWeather(city) {
try {
const response = await fetch(`https://api.weather.com/${city}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('天気の取得に失敗:', error.message);
return null;
}
}
// 使い方
const weather = await getWeather('tokyo');
例2: 複数のAPIを順番に呼ぶ
async function getUserPosts(userId) {
// まずユーザー情報を取得
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
// ユーザー情報をもとに投稿を取得
const posts = await fetch(`/api/posts?author=${user.name}`).then(r => r.json());
return { user, posts };
}
例3: 複数のAPIを同時に呼ぶ(Promise.all)
async function getDashboardData() {
// 3つのAPIを同時に呼ぶ(待ち時間短縮)
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
]);
return { users, posts, comments };
}
Promise.all を使うと、独立した複数の処理を並列実行できます。取得したデータを配列メソッドで加工する方法については、JavaScriptの配列メソッド入門 ― map・filter・reduceの使い方で解説しています。
よくあるエラーと対処法
エラー1: await is only valid in async functions
// NG
function main() {
const data = await fetchData(); // エラー
}
// OK: asyncを付ける
async function main() {
const data = await fetchData();
}
APIから取得するデータはJSON形式であることがほとんどです。JSONの基本を知っておくとデバッグがしやすくなります。
エラー2: awaitを付け忘れてPromiseオブジェクトが返る
async function example() {
// NG: awaitがないのでPromiseオブジェクトが入る
const data = fetch('/api/data');
console.log(data); // Promise { <pending> }
// OK: awaitを付ける
const data2 = await fetch('/api/data');
const json = await data2.json();
console.log(json); // 実際のデータ
}
よくある質問(FAQ)
Q: async/awaitとPromiseの.then()はどちらを使うべきですか?
A: 基本的にはasync/awaitが読みやすくておすすめです。ただし、Promise.all のように複数の非同期処理を並列実行する場合は、Promiseの機能と組み合わせて使います。どちらか一方ではなく、場面に応じて使い分けるのがベストです。
Q: await を付け忘れるとどうなりますか?
A: Promiseオブジェクトがそのまま変数に入ります。console.log すると Promise { <pending> } と表示されます。データが取れていないのに処理が進んでしまうので、バグの原因になります。
Q: async関数の中で return した値はどうなりますか?
A: 自動的にPromiseでラップされて返されます。呼び出し側では await を付けて受け取るか、.then() で値を取り出します。
Q: エラー処理は try/catch 以外の方法はありますか?
A: async関数の戻り値はPromiseなので、呼び出し側で .catch() を使うこともできます。ただし、関数内で完結させたい場合は try/catch の方が分かりやすいです。
Q: トップレベル(関数の外)で await は使えますか?
A: ES2022以降のモジュール(<script type="module"> やNode.jsのESモジュール)ではトップレベル await が使えます。通常のスクリプトでは使えないので、async 関数で囲む必要があります。
まとめ
- 非同期処理は「待っている間に他のことをする」仕組み
asyncを関数に付けると、その中でawaitが使えるawaitはPromiseの完了を待って結果を返す- エラー処理は
try/catchで囲む - 独立した処理は
Promise.allで並列実行すると速い
あわせて読みたい
関連リソース
JavaScriptをもっと学びたい方へ: