JavaScriptのプロミスエコシステム:.thenチェインからasync/awaitへの移行

JavaScriptのプロミスエコシステム:.thenチェインからasync/awaitへの移行

JavaScriptの非同期処理は、.thenチェインとasync/awaitという2つの主要なアプローチを中心に進化してきました。本記事では、.thenチェインの仕組みと限界、async/awaitへの移行の利点、具体的な使用例を通じてその実用性を解説します。

プロミスの基礎

プロミスは、非同期処理の結果を表すオブジェクトです。状態は「pending」、「fulfilled」、「rejected」の3つのいずれかです。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('成功'), 1000);
});

.thenチェインの基本構造

.thenメソッドは、プロミスがfulfilledされた際に実行されるコールバックを登録します。

promise
  .then(result => {
    console.log(result); // '成功'
    return '次の処理';
  })
  .then(nextResult => {
    console.log(nextResult); // '次の処理'
  })
  .catch(error => {
    console.error(error);
  });

エラーハンドリングの複雑化

.thenチェインは、エラーハンドリングを一箇所で行える反面、長いチェインでは可読性が低下します。

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) throw new Error('ネットワークエラー');
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('エラー:', error);
  });

async/awaitの登場

async/awaitは、プロミスをより直感的に扱える構文を提供します。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error('ネットワークエラー');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('エラー:', error);
  }
}
fetchData();

可読性の向上

async/awaitは、非同期処理を同期処理のように記述できるため、コードの可読性が向上します。

ネストの解消

.thenチェインで発生しがちなネストを解消し、よりフラットなコード構造を実現します。

async function processItems(items) {
  for (const item of items) {
    const result = await asyncOperation(item);
    console.log(result);
  }
}

並列処理とasync/await

Promise.allを組み合わせることで、並列処理も可能です。

async function fetchMultipleData(urls) {
  try {
    const results = await Promise.all(urls.map(url => fetch(url).then(res => res.json())));
    console.log(results);
  } catch (error) {
    console.error('エラー:', error);
  }
}

async/awaitでのエラーハンドリング

try-catchを使用することで、エラー処理が一箇所に集約され、管理が容易になります。

レガシーコードの変換

.thenチェインをasync/awaitに変換することで、既存コードの可読性を向上させることができます。

// 旧コード
function oldFunction() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data));
}

// 新コード
async function newFunction() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
}

async/awaitの注意点

awaitの乱用は、非同期処理全体のパフォーマンスを低下させる可能性があります。

async function inefficientFunction(items) {
  for (const item of items) {
    const result = await asyncOperation(item); // 並列処理ではない
    console.log(result);
  }
}

// 改善版
async function efficientFunction(items) {
  const results = await Promise.all(items.map(item => asyncOperation(item)));
  console.log(results);
}

非同期イテレーターの活用

非同期処理を効率的に反復処理するために、非同期イテレーターを利用できます。

async function* fetchItems(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    yield await response.json();
  }
}

(async () => {
  for await (const item of fetchItems(['url1', 'url2', 'url3'])) {
    console.log(item);
  }
})();

まとめ

.thenチェインからasync/awaitへの移行は、JavaScriptの非同期処理の可読性とメンテナンス性を大きく向上させます。適切な構文と設計を選択することで、より効率的で分かりやすいコードを実現できます。