Node.jsで非同期処理を書く際、Promiseは欠かせない存在です。しかし、Promise.all()Promise.allSettled()のどちらを使うべきか、コールバック形式のAPIをPromise化するにはどうすればよいか、といった実践的な疑問を持つ方も多いでしょう。

本記事では、Node.js環境におけるPromiseの活用パターンを網羅的に解説します。複数のPromise処理を効率的に扱う静的メソッドの使い分けから、util.promisify()によるコールバックAPI変換、fs/promisesなどの組み込みPromise APIまで、可読性が高く保守しやすい非同期コードを書くための知識を習得できます。

実行環境

項目 バージョン
Node.js 20.x LTS以上
npm 10.x以上
OS Windows/macOS/Linux

前提条件

  • JavaScriptの基礎知識(関数、オブジェクト、配列操作)
  • Promiseの基本(resolve、reject、then、catch)の理解
  • Node.jsの基本操作経験

複数のPromiseを扱う静的メソッド

Node.jsでは、複数の非同期処理を効率的に実行するシナリオが頻繁に発生します。Promiseの静的メソッドを使い分けることで、要件に応じた最適な処理が可能になります。

メソッド一覧と特徴

メソッド 履行条件 拒否条件 主なユースケース
Promise.all() すべて成功 1つでも失敗 依存関係のある一括処理
Promise.allSettled() すべて決定後、常に履行 なし 独立した処理の結果収集
Promise.race() 最初に決定 最初に決定 タイムアウト実装
Promise.any() 最初に成功 すべて失敗 フォールバック処理

Promise.all() - すべての成功を待つ

Promise.all()は、渡されたすべてのPromiseが成功したときに履行されます。1つでも失敗すると、即座に拒否されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { readFile } from 'node:fs/promises';

async function loadAllConfigs() {
  try {
    const [dbConfig, appConfig, logConfig] = await Promise.all([
      readFile('./config/database.json', 'utf8'),
      readFile('./config/app.json', 'utf8'),
      readFile('./config/logging.json', 'utf8')
    ]);
    
    return {
      database: JSON.parse(dbConfig),
      app: JSON.parse(appConfig),
      logging: JSON.parse(logConfig)
    };
  } catch (error) {
    // いずれかのファイル読み込みが失敗した場合
    console.error('設定ファイルの読み込みに失敗しました:', error.message);
    throw error;
  }
}

Promise.all()は、すべての処理が成功することが前提の場合に適しています。上記の例では、3つの設定ファイルすべてが揃わないとアプリケーションが動作しないため、1つでも失敗したら全体を失敗として扱います。

Promise.allSettled() - 全結果を収集する

Promise.allSettled()は、すべてのPromiseが決定(成功または失敗)するまで待ちます。結果はすべてfulfilledまたはrejectedのステータスとして返されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { stat } from 'node:fs/promises';

async function checkFilesExist(filePaths) {
  const results = await Promise.allSettled(
    filePaths.map(filePath => stat(filePath))
  );
  
  const report = results.map((result, index) => ({
    path: filePaths[index],
    exists: result.status === 'fulfilled',
    error: result.status === 'rejected' ? result.reason.code : null
  }));
  
  console.log('ファイル存在チェック結果:');
  report.forEach(item => {
    if (item.exists) {
      console.log(`  [存在] ${item.path}`);
    } else {
      console.log(`  [不在] ${item.path} (${item.error})`);
    }
  });
  
  return report;
}

// 使用例
await checkFilesExist([
  './package.json',
  './README.md',
  './not-exist.txt'
]);

実行結果は以下のようになります。

ファイル存在チェック結果:
  [存在] ./package.json
  [存在] ./README.md
  [不在] ./not-exist.txt (ENOENT)

Promise.allSettled()は、各処理が独立しており、一部が失敗しても他の結果を知りたい場合に適しています。バッチ処理やヘルスチェックなどで威力を発揮します。

Promise.race() - 最速の結果を採用

Promise.race()は、最初に決定したPromiseの結果(成功でも失敗でも)を返します。タイムアウト処理の実装に最適です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`処理がタイムアウトしました (${ms}ms)`));
    }, ms);
  });
  
  return Promise.race([promise, timeout]);
}

async function fetchDataWithTimeout(url) {
  try {
    const response = await withTimeout(
      fetch(url),
      5000 // 5秒でタイムアウト
    );
    return await response.json();
  } catch (error) {
    if (error.message.includes('タイムアウト')) {
      console.error('リクエストがタイムアウトしました');
    }
    throw error;
  }
}

Node.jsではAbortControllerを使ったタイムアウト実装も推奨されますが、Promise.race()を使ったパターンは理解しやすく、さまざまな場面で応用できます。

Promise.any() - 最初の成功を採用

Promise.any()は、最初に成功したPromiseの結果を返します。すべて失敗した場合のみAggregateErrorで拒否されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
async function fetchFromMirrors(resourcePath) {
  const mirrors = [
    'https://mirror1.example.com',
    'https://mirror2.example.com',
    'https://mirror3.example.com'
  ];
  
  try {
    const response = await Promise.any(
      mirrors.map(mirror => 
        fetch(`${mirror}${resourcePath}`).then(res => {
          if (!res.ok) throw new Error(`HTTP ${res.status}`);
          return res;
        })
      )
    );
    
    console.log('ダウンロード成功:', response.url);
    return await response.json();
  } catch (error) {
    if (error instanceof AggregateError) {
      console.error('すべてのミラーからの取得に失敗しました:');
      error.errors.forEach((err, i) => {
        console.error(`  ${mirrors[i]}: ${err.message}`);
      });
    }
    throw error;
  }
}

Promise.any()はフォールバック機能を実装する際に便利です。複数のソースから最初に応答があったものを使用したい場合に活用できます。

使い分けの判断フローチャート

flowchart TD
    A[複数のPromiseを実行] --> B{すべて成功が必須?}
    B -->|はい| C[Promise.all]
    B -->|いいえ| D{各結果の詳細が必要?}
    D -->|はい| E[Promise.allSettled]
    D -->|いいえ| F{最初の成功だけでよい?}
    F -->|はい| G[Promise.any]
    F -->|いいえ| H[Promise.race]

Promiseのエラーハンドリング

Promiseベースの非同期処理では、適切なエラーハンドリングが重要です。エラーを適切に捕捉し、必要な情報を含めてログ出力や上位への伝播を行う必要があります。

基本的なエラーハンドリングパターン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { readFile } from 'node:fs/promises';

// パターン1: async/awaitとtry-catch
async function readConfig(configPath) {
  try {
    const content = await readFile(configPath, 'utf8');
    return JSON.parse(content);
  } catch (error) {
    if (error.code === 'ENOENT') {
      console.error(`設定ファイルが見つかりません: ${configPath}`);
      return null; // デフォルト値を返す
    }
    if (error instanceof SyntaxError) {
      console.error(`JSONパースエラー: ${configPath}`);
      throw new Error(`設定ファイルの形式が不正です: ${configPath}`);
    }
    throw error; // その他のエラーは再スロー
  }
}

// パターン2: catchメソッドチェーン
function readConfigWithChain(configPath) {
  return readFile(configPath, 'utf8')
    .then(content => JSON.parse(content))
    .catch(error => {
      console.error('設定読み込みエラー:', error.message);
      throw error;
    });
}

エラーの型による分岐処理

Node.jsのエラーには、発生源に応じたプロパティが含まれています。これを活用することで、エラーの種類に応じた適切な処理が可能になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { access, readFile, constants } from 'node:fs/promises';

async function safeReadFile(filePath) {
  try {
    await access(filePath, constants.R_OK);
    return await readFile(filePath, 'utf8');
  } catch (error) {
    // システムエラーの判別
    if (error.code === 'ENOENT') {
      throw new Error(`ファイルが存在しません: ${filePath}`);
    }
    if (error.code === 'EACCES') {
      throw new Error(`読み取り権限がありません: ${filePath}`);
    }
    if (error.code === 'EISDIR') {
      throw new Error(`ディレクトリは読み取れません: ${filePath}`);
    }
    
    // 予期しないエラー
    throw new Error(`ファイル読み取り中に不明なエラー: ${error.message}`);
  }
}

複数Promiseでのエラー収集

Promise.allSettled()を使用すると、すべての処理結果(成功・失敗両方)を収集できます。これにより、バッチ処理での詳細なエラーレポートが可能になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { writeFile } from 'node:fs/promises';

async function batchWriteFiles(files) {
  const results = await Promise.allSettled(
    files.map(({ path, content }) => 
      writeFile(path, content, 'utf8')
    )
  );
  
  const summary = {
    total: files.length,
    success: 0,
    failed: 0,
    errors: []
  };
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      summary.success++;
    } else {
      summary.failed++;
      summary.errors.push({
        path: files[index].path,
        reason: result.reason.message
      });
    }
  });
  
  console.log(`処理完了: ${summary.success}/${summary.total} 成功`);
  
  if (summary.failed > 0) {
    console.error('失敗したファイル:');
    summary.errors.forEach(err => {
      console.error(`  ${err.path}: ${err.reason}`);
    });
  }
  
  return summary;
}

未処理のPromise拒否を検知する

Node.jsでは、未処理のPromise拒否を検知するイベントハンドラを設定できます。本番環境でのデバッグやエラー監視に活用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 未処理のPromise拒否を検知
process.on('unhandledRejection', (reason, promise) => {
  console.error('未処理のPromise拒否が検出されました:');
  console.error('Promise:', promise);
  console.error('理由:', reason);
  // 本番環境ではエラー監視サービスへの送信など
});

// 警告として扱われたPromise拒否
process.on('rejectionHandled', (promise) => {
  console.warn('遅れてハンドルされたPromise拒否:', promise);
});

util.promisify()によるコールバックAPI変換

Node.jsの従来のAPIは、コールバック形式(エラーファーストコールバック)で設計されています。util.promisify()を使用すると、これらをPromiseベースの関数に変換できます。

基本的な使い方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { promisify } from 'node:util';
import { exec } from 'node:child_process';

// execをPromise化
const execAsync = promisify(exec);

async function runCommand(command) {
  try {
    const { stdout, stderr } = await execAsync(command);
    if (stderr) {
      console.warn('標準エラー出力:', stderr);
    }
    return stdout.trim();
  } catch (error) {
    console.error(`コマンド実行エラー: ${error.message}`);
    throw error;
  }
}

// 使用例
const nodeVersion = await runCommand('node --version');
console.log('Node.jsバージョン:', nodeVersion);

promisifyが適用可能な関数の条件

util.promisify()は、以下の条件を満たす関数に適用できます。

  1. 最後の引数がコールバック関数である
  2. コールバックの第1引数がエラー(またはnull)である
  3. コールバックの第2引数以降が成功時の結果である
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { promisify } from 'node:util';

// promisify可能な関数の例
function legacyApi(param1, param2, callback) {
  setTimeout(() => {
    if (param1 < 0) {
      callback(new Error('param1は0以上である必要があります'));
    } else {
      callback(null, param1 + param2);
    }
  }, 100);
}

const legacyApiAsync = promisify(legacyApi);

// 使用例
const result = await legacyApiAsync(5, 3);
console.log('結果:', result); // 結果: 8

カスタムpromisify実装

コールバックの形式が標準と異なる場合、util.promisify.customシンボルを使用してカスタム実装を提供できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { promisify } from 'node:util';

// 非標準のコールバック形式(成功/失敗が別コールバック)
function fetchData(url, onSuccess, onError) {
  setTimeout(() => {
    if (url.startsWith('http')) {
      onSuccess({ data: 'レスポンスデータ', status: 200 });
    } else {
      onError(new Error('無効なURL'));
    }
  }, 100);
}

// カスタムpromisify実装を定義
fetchData[promisify.custom] = (url) => {
  return new Promise((resolve, reject) => {
    fetchData(url, resolve, reject);
  });
};

const fetchDataAsync = promisify(fetchData);

// 使用例
const response = await fetchDataAsync('https://api.example.com/data');
console.log('レスポンス:', response);

複数の値を返すコールバックの処理

一部のNode.js APIは、コールバックで複数の値を返します。この場合、promisifyは最初の値のみを返すため、必要に応じてラッパー関数を作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { promisify } from 'node:util';
import dns from 'node:dns';

// dns.lookupは (err, address, family) を返す
const lookupAsync = promisify(dns.lookup);

// 単一の値として取得(addressのみ)
const address = await lookupAsync('nodejs.org');
console.log('IPアドレス:', address);

// すべての値が必要な場合はオプションを使用
const result = await dns.promises.lookup('nodejs.org', { verbatim: true });
console.log('詳細結果:', result); // { address: '...', family: 4 }

Node.js組み込みAPIのPromise版

Node.js 10以降、多くの組み込みモジュールにPromise版が追加されています。新しいコードでは、コールバック版よりもPromise版を使用することを推奨します。

fs/promises - ファイルシステム操作

fs/promisesモジュールは、ファイルシステム操作のPromise版APIを提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import {
  readFile,
  writeFile,
  mkdir,
  readdir,
  stat,
  rm,
  copyFile
} from 'node:fs/promises';
import { join } from 'node:path';

// ファイル読み込み
const content = await readFile('./package.json', 'utf8');
const pkg = JSON.parse(content);
console.log('パッケージ名:', pkg.name);

// ファイル書き込み
await writeFile('./output.txt', 'Hello, Node.js!', 'utf8');

// ディレクトリ作成(再帰的)
await mkdir('./nested/deep/directory', { recursive: true });

// ディレクトリ内容の読み取り
const files = await readdir('./src', { withFileTypes: true });
files.forEach(entry => {
  const type = entry.isDirectory() ? 'ディレクトリ' : 'ファイル';
  console.log(`${type}: ${entry.name}`);
});

// ファイル情報の取得
const fileStats = await stat('./package.json');
console.log('ファイルサイズ:', fileStats.size, 'bytes');
console.log('最終更新日:', fileStats.mtime);

// ファイルコピー
await copyFile('./source.txt', './destination.txt');

// ファイル/ディレクトリ削除(再帰的)
await rm('./temp', { recursive: true, force: true });

FileHandleによるストリーム処理

大きなファイルを扱う場合、FileHandleを使用したストリーム処理が効率的です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { open } from 'node:fs/promises';

async function processLargeFile(filePath) {
  const fileHandle = await open(filePath, 'r');
  
  try {
    // 行ごとに読み込み
    for await (const line of fileHandle.readLines()) {
      // 各行を処理
      console.log('行:', line);
    }
  } finally {
    await fileHandle.close();
  }
}

// Explicit Resource Management(Node.js 20.4+)
async function processWithUsing(filePath) {
  await using fileHandle = await open(filePath, 'r');
  
  const content = await fileHandle.readFile('utf8');
  return content;
  // fileHandleは自動的にクローズされる
}

dns/promises - DNS解決

DNS解決のPromise版APIはdns.promisesまたはdns/promisesで利用可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import dns from 'node:dns/promises';

async function resolveDomain(hostname) {
  try {
    // IPアドレスの解決
    const addresses = await dns.resolve4(hostname);
    console.log(`${hostname} のIPv4アドレス:`, addresses);
    
    // MXレコードの取得
    const mxRecords = await dns.resolveMx(hostname);
    console.log('MXレコード:', mxRecords);
    
    // 逆引き
    const hostnames = await dns.reverse(addresses[0]);
    console.log('逆引き結果:', hostnames);
    
    return { addresses, mxRecords };
  } catch (error) {
    if (error.code === 'ENOTFOUND') {
      console.error(`ドメインが見つかりません: ${hostname}`);
    }
    throw error;
  }
}

timers/promises - タイマー操作

Node.js 16以降、タイマーのPromise版が利用可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {
  setTimeout as sleep,
  setInterval
} from 'node:timers/promises';

// 指定時間待機
console.log('処理開始');
await sleep(1000);
console.log('1秒経過');

// 値を返すsleep
const result = await sleep(500, '待機完了');
console.log(result); // "待機完了"

// AbortControllerによるキャンセル
const controller = new AbortController();

setTimeout(() => controller.abort(), 2000);

try {
  await sleep(5000, null, { signal: controller.signal });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('タイマーがキャンセルされました');
  }
}

// 定期実行(AsyncIterator)
const interval = setInterval(1000, 'tick');
let count = 0;

for await (const value of interval) {
  console.log(`${value}: ${++count}回目`);
  if (count >= 3) break;
}

stream/promises - ストリーム完了待機

ストリーム処理の完了をPromiseで待機できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { createGzip, createGunzip } from 'node:zlib';

// ファイル圧縮
async function compressFile(inputPath, outputPath) {
  await pipeline(
    createReadStream(inputPath),
    createGzip(),
    createWriteStream(outputPath)
  );
  console.log(`圧縮完了: ${outputPath}`);
}

// ファイル解凍
async function decompressFile(inputPath, outputPath) {
  await pipeline(
    createReadStream(inputPath),
    createGunzip(),
    createWriteStream(outputPath)
  );
  console.log(`解凍完了: ${outputPath}`);
}

// 使用例
await compressFile('./large-file.txt', './large-file.txt.gz');
await decompressFile('./large-file.txt.gz', './restored-file.txt');

その他のPromise対応モジュール

モジュール インポート方法 主な用途
fs/promises import { readFile } from 'node:fs/promises' ファイル操作
dns/promises import dns from 'node:dns/promises' DNS解決
timers/promises import { setTimeout } from 'node:timers/promises' タイマー
stream/promises import { pipeline } from 'node:stream/promises' ストリーム処理
readline/promises import { createInterface } from 'node:readline/promises' 対話型入力

実践的なパターン集

並列処理の制限(セマフォパターン)

大量の非同期処理を行う際、同時実行数を制限することでリソース枯渇を防ぎます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Semaphore {
  constructor(maxConcurrent) {
    this.maxConcurrent = maxConcurrent;
    this.running = 0;
    this.queue = [];
  }

  async acquire() {
    if (this.running < this.maxConcurrent) {
      this.running++;
      return;
    }

    await new Promise(resolve => this.queue.push(resolve));
    this.running++;
  }

  release() {
    this.running--;
    if (this.queue.length > 0) {
      const next = this.queue.shift();
      next();
    }
  }
}

async function processWithLimit(items, processor, maxConcurrent = 5) {
  const semaphore = new Semaphore(maxConcurrent);
  
  const tasks = items.map(async (item) => {
    await semaphore.acquire();
    try {
      return await processor(item);
    } finally {
      semaphore.release();
    }
  });
  
  return Promise.all(tasks);
}

// 使用例:最大3並列でファイルを処理
import { readFile } from 'node:fs/promises';

const files = ['file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', 'file5.txt'];
const contents = await processWithLimit(
  files,
  (file) => readFile(file, 'utf8'),
  3
);

リトライ処理

一時的なエラーに対してリトライを行うパターンです。指数バックオフを使用して、サーバーへの負荷を軽減します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
async function withRetry(fn, options = {}) {
  const {
    maxAttempts = 3,
    initialDelay = 1000,
    maxDelay = 30000,
    backoffFactor = 2,
    shouldRetry = () => true
  } = options;
  
  let lastError;
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn(attempt);
    } catch (error) {
      lastError = error;
      
      if (attempt === maxAttempts || !shouldRetry(error)) {
        throw error;
      }
      
      const delay = Math.min(
        initialDelay * Math.pow(backoffFactor, attempt - 1),
        maxDelay
      );
      
      console.log(`試行 ${attempt} 失敗。${delay}ms後にリトライ...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

// 使用例
const data = await withRetry(
  async (attempt) => {
    console.log(`API呼び出し試行: ${attempt}`);
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },
  {
    maxAttempts: 5,
    shouldRetry: (error) => {
      // 5xx系エラーのみリトライ
      return error.message.includes('50');
    }
  }
);

キャッシュ付き非同期処理

同じ引数での呼び出しをキャッシュして、不要なAPI呼び出しを削減します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function memoizeAsync(fn, options = {}) {
  const {
    maxAge = 60000, // キャッシュ有効期間(ミリ秒)
    keyResolver = (...args) => JSON.stringify(args)
  } = options;
  
  const cache = new Map();
  
  return async function (...args) {
    const key = keyResolver(...args);
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < maxAge) {
      console.log('キャッシュヒット:', key);
      return cached.value;
    }
    
    console.log('キャッシュミス:', key);
    const value = await fn.apply(this, args);
    
    cache.set(key, {
      value,
      timestamp: Date.now()
    });
    
    return value;
  };
}

// 使用例
const fetchUserCached = memoizeAsync(
  async (userId) => {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    return response.json();
  },
  { maxAge: 5 * 60 * 1000 } // 5分間キャッシュ
);

// 同じユーザーへの呼び出しはキャッシュから返される
const user1 = await fetchUserCached(123);
const user2 = await fetchUserCached(123); // キャッシュヒット

まとめ

本記事では、Node.jsにおけるPromise活用の実践的なパターンを解説しました。

  • 複数Promiseの静的メソッド: Promise.all()Promise.allSettled()Promise.race()Promise.any()を要件に応じて使い分けることで、効率的な非同期処理が可能になります
  • エラーハンドリング: エラーの種類に応じた分岐処理と、未処理拒否の検知により、堅牢なアプリケーションを構築できます
  • util.promisify(): レガシーなコールバックAPIをPromise化し、モダンな非同期コードに統一できます
  • 組み込みPromise API: fs/promisesdns/promisestimers/promisesなどを活用することで、より簡潔なコードが書けます
  • 実践パターン: セマフォによる並列制限、リトライ処理、キャッシュなど、実務で必要なパターンを習得できます

Promiseを適切に活用することで、コールバック地獄を回避し、可読性が高く保守しやすい非同期コードを実現できます。次のステップとして、async/awaitのより高度な使い方やパフォーマンス最適化について学ぶことをお勧めします。

参考リンク