はじめに
JavaScriptで非同期処理を扱う際、従来はPromiseを使って記述していました。しかし、処理が複雑になるとコードが読みにくくなることがあります。そこで登場したのがasync/awaitです。
非同期処理とは、時間のかかる処理(APIリクエストやファイル読み込みなど)を待たずに次の処理を進める仕組みのことです。async/awaitを使うことで、非同期処理をより直感的に、同期処理のような見た目で書けるようになります。
Async Functionの基本
async functionの定義方法
関数の前にasyncキーワードを付けるだけで、その関数はasync functionになります。
// 通常の関数
function normalFunction() {
return "hello";
}
// async function
async function asyncFunction() {
return "hello";
}
async functionは必ずPromiseを返す
async functionの重要な特徴は、必ずPromiseオブジェクトを返すという点です。たとえ関数内で普通の値を返しても、自動的にPromiseでラップされます。
async function getMessage() {
return "こんにちは";
}
// 実行するとPromiseが返る
const result = getMessage();
console.log(result); // Promise { 'こんにちは' }
// 値を取得するにはthenを使う
getMessage().then(message => {
console.log(message); // "こんにちは"
});
returnとresolveの関係
async function内でreturnした値は、自動的にPromiseのresolveとして扱われます。
async function fetchData() {
return { id: 1, name: "太郎" };
}
// これは以下のPromiseと同じ動作
function fetchDataPromise() {
return Promise.resolve({ id: 1, name: "太郎" });
}
throw new Errorとrejectの関係
同様に、async function内でthrowしたエラーは、Promiseのrejectとして扱われます。
async function fetchData() {
throw new Error("データの取得に失敗しました");
}
// これは以下のPromiseと同じ動作
function fetchDataPromise() {
return Promise.reject(new Error("データの取得に失敗しました"));
}
Promiseとの違い
同じ処理をPromiseとasync/awaitで書き比べてみましょう。
Promiseを使った従来の書き方
function getUserData(userId) {
return fetch(`https://api.example.com/users/${userId}`)
.then(response => {
return response.json();
})
.then(user => {
return fetch(`https://api.example.com/posts/${user.id}`);
})
.then(response => {
return response.json();
})
.then(posts => {
console.log(posts);
return posts;
})
.catch(error => {
console.error("エラーが発生しました:", error);
});
}
async/awaitを使った書き方
async function getUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const user = await response.json();
const postsResponse = await fetch(`https://api.example.com/posts/${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
return posts;
} catch (error) {
console.error("エラーが発生しました:", error);
}
}
処理の流れを図で表すと以下のようになります。
async/awaitを使うと、.then()のチェーンが不要になり、コードが上から下へ読みやすくなりますね。
awaitでコードをシンプルに
awaitの役割
awaitキーワードは、Promiseの結果が確定するまで処理を待機します。これによって非同期処理を同期処理のように書けるようになります。
重要な制約: awaitは必ずasync function内でのみ使用できます。
thenのコールバック地獄を解消
複数の非同期処理を順番に実行する場合、Promiseだけで書くとネストが深くなりがちです。
// Promiseでのネスト(コールバック地獄)
function processData() {
fetchUser()
.then(user => {
fetchPosts(user.id)
.then(posts => {
fetchComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});
}
// async/awaitでフラットに
async function processData() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log(comments);
}
await結果を変数に格納する方法
awaitした結果はletやconstで変数に格納できます。これによって、APIのレスポンスデータを後続の処理で使いまわせます。
async function displayUserInfo() {
// APIリクエストの結果を変数に格納
const userResponse = await fetch("https://api.example.com/user/123");
const userData = await userResponse.json();
// 取得したデータを使って別のリクエスト
const postsResponse = await fetch(`https://api.example.com/posts?userId=${userData.id}`);
const posts = await postsResponse.json();
// 両方のデータを使って処理
return {
user: userData,
postCount: posts.length
};
}
まとめ
async/awaitを使うメリット
- 可読性の向上: 非同期処理を同期処理のように書けるため、コードが読みやすい
- 変数の扱いが簡単: awaitの結果を変数に格納して、後続の処理で使える
- エラーハンドリングが直感的: try-catchで統一的にエラー処理できる
-
コールバック地獄の回避:
.then()のネストが不要になる
使用時の注意点
- async functionは常にPromiseを返すため、呼び出し側でもawaitまたは
.then()が必要 - awaitはasync function内でのみ使用可能
- awaitを使うと処理が順次実行されるため、並列実行が必要な場合は
Promise.all()を併用する
// 並列実行の例
async function fetchMultipleUsers() {
// これらは並列で実行される
const [user1, user2, user3] = await Promise.all([
fetch("https://api.example.com/users/1").then(r => r.json()),
fetch("https://api.example.com/users/2").then(r => r.json()),
fetch("https://api.example.com/users/3").then(r => r.json())
]);
return [user1, user2, user3];
}
async/awaitを使いこなすことで、非同期処理をよりシンプルかつ保守しやすいコードで実装できます。まずは簡単な例から試してみて、徐々に複雑な処理にも適用していきましょう。