非同期が必要な理由
アプリケーションが外部APIにリクエストを送信し、2秒で応答したとします。非同期性がなければ、プログラムはこの間、他の操作の実行をブロックしてフリーズするだけです。非同期コードを使用すると、バックグラウンドで操作が実行されている間もアプリケーションを実行し続けることができます。
コールバックからプロミスと非同期/待機へ
JavaScriptの非同期コードの進化は、いくつかの段階を経ています。
コールバック関数(旧式アプローチ):
fetchUser(userId, (error, user) => {
if (error) {
console.error(error);
return;
}
fetchPosts(user.id, (error, posts) => {
if (error) {
console.error(error);
return;
}
// コールバックヘルはここから始まります
});
});約束(改善):
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error(error));Async/Await(最新のアプローチ):
async function getUserPosts(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error(error);
}
}async/awaitの基礎
関数の前にキーワードasyncを指定すると、関数は常にpromiseを返します。キーワード await は、実行を続行する前に、JavaScriptに約束の結果を待つようにします。
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}重要なルール:
awaitは、async関数内でのみ使用できますasync関数は常にpromiseを返します関数が値を返す場合、その値は自動的に
Promise.resolve()にラップされます。
エラー処理
Try-catchブロックは、async/awaitでエラーを処理する自然な方法です。
async function processOrder(orderId) {
try {
const order = await fetchOrder(orderId);
const payment = await processPayment(order);
const confirmation = await sendConfirmation(payment);
return confirmation;
} catch (error) {
if (error.code === 'PAYMENT_FAILED') {
await refundOrder(orderId);
}
throw new Error(`Order processing failed: ${error.message}`);
}
}より細かい処理のために.catch()と組み合わせることができます。
async function getData() {
const data = await fetchData().catch(error => {
console.error('Fetch failed:', error);
return getDefaultData(); // デフォルトデータを復元しています
});
return data;
}
並列実行
よくある間違いの1つは、独立した操作を順番に実行することです。
// 悪い:操作は順番に実行されます(6秒)
async function loadData() {
const users = await fetchUsers(); // 3 秒
const posts = await fetchPosts(); // 3 秒
return { users, posts };
}並列実行にはPromise.all()を使用します。
// 良い:操作は並行して実行されます(3秒)
async function loadData() {
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts()
]);
return { users, posts };
}Promise.allSettled()
すべての操作の結果が必要な場合:
async function loadAllData() {
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Operation ${index} succeeded:`, result.value);
} else {
console.error(`Operation ${index} failed:`, result.reason);
}
});
}Promise.race()
タイムアウト操作の場合:
async function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
);
return Promise.race([
fetch(url),
timeoutPromise
]);
}ループ内の非同期/待機
サイクルには注意してください。予期しない動作を引き起こす可能性があります。
// 連続処理
async function processSequentially(items) {
for (const item of items) {
await processItem(item); // 各操作の完了を待っています
}
}
// 並列処理
async function processInParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
// バッチ処理
async function processBatches(items, batchSize = 5) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(batch.map(item => processItem(item)));
}
}一般的なエラー
1. 忘れられたawait
// エラー:関数はデータではなくpromiseを返します
async function getData() {
return fetchData(); // await を忘れました
}
// 正解
async function getData() {
return await fetchData();
}2. forEach での await の使用
// 動作しません: forEach は async を理解しません
items.forEach(async (item) => {
await processItem(item);
});
// for...ofを使用する
for (const item of items) {
await processItem(item);
}3. 制御なしでサイクル内にプロミスを作成する
// 数千の同時リクエストを作成します
const promises = items.map(item => fetchItem(item));
await Promise.all(promises);
// 並列処理を制御するのが良い高度なパターン
指数関数的な遅延で再試行:
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}結果のキャッシュ:
const cache = new Map();
async function fetchWithCache(key, fetcher) {
if (cache.has(key)) {
return cache.get(key);
}
const result = await fetcher();
cache.set(key, result);
return result;
}非同期関数のデバウンス:
function asyncDebounce(func, wait) {
let timeout;
return function(...args) {
return new Promise((resolve) => {
clearTimeout(timeout);
timeout = setTimeout(async () => {
resolve(await func.apply(this, args));
}, wait);
});
};
}他の言語での非同期/待機
非同期/待機の概念は、JavaScriptだけではありません。
Python:
async def fetch_data():
response = await aiohttp.get('https://api.example.com')
return await response.json()C#:
async Task<string> FetchDataAsync() {
var response = await httpClient.GetAsync("https://api.example.com");
return await response.Content.ReadAsStringAsync();
}Rust:
async fn fetch_data() -> Result<String, Error> {
let response = reqwest::get("https://api.example.com").await?;
Ok(response.text().await?)
}パフォーマンスとベストプラクティス
1. 過度のawaitを避ける
// 最適ではありません
async function process() {
const result = await computeValue();
return result; // 余分なawait
}
// 良い
async function process() {
return computeValue();
}2. 可能な限り並列処理を使用する
// 遅い
const user = await getUser();
const posts = await getPosts();
// より速く
const [user, posts] = await Promise.all([getUser(), getPosts()]);3. 外部リクエストにタイムアウトを追加する
async function safeFetch(url) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
return response;
} finally {
clearTimeout(timeout);
}
}結論
Async/awaitは、コールバックヘルの問題を解決し、エラー処理を簡素化することで、非同期コードを読みやすく、理解しやすくします。重要なポイント:
連続した操作にはasync/awaitを使用する
並列実行にはPromise.all ()を使用する
try-catchを介してエラーを処理することを忘れないでください
サイクルには注意して、必要な場所で常にawaitを使用してください
システムの過負荷を避けるために並列処理を制御する
操作を順番に実行する必要がある場合と並行して実行する必要がある場合の実践と理解は、効果的な非同期コードを書くのに役立ちます。
コディック JavaScript、Python、C、Rust、その他多くの言語の構造化されたコースを提供しており、非同期プログラミングの基礎から高度なパターンまで学ぶことができます。実践的なタスクを含むインタラクティブなレッスンは、実際の例で資料を強化するのに役立ちます。
async/await について質問がある場合や、難しい課題に遭遇した場合は、 当社のTelegramチャンネル経験豊富な開発者と活発なコミュニティが、構文の複雑さからアーキテクチャの解決策まで、あらゆる問題を解決するのに常に役立ちます。質問をしたり、経験を共有したり、コミュニティと一緒に学んだりしましょう!
