Promiseの概要と使い方

Promise

コールバックのネスト問題

複数層のコールバック関数が入れ子になっている状態を「コールバック地獄」と呼びます。以下のコードはその例です。

setTimeout(() => { // 第1層のコールバック
  console.log('1秒後に出力');
  setTimeout(() => { // 第2層のコールバック
    console.log('さらに2秒後に出力');
    setTimeout(() => { // 第3層のコールバック
      console.log('さらに3秒後に出力');
    }, 3000);
  }, 2000);
}, 1000);
  • コールバック地獄の問題点: ⚫ コードの結合度が高すぎて、変更が広範囲に影響を与えるため保守性が低い ⚫ 深いネストにより可読性が低下する

この問題を解決するために、ES6(ECMAScript 2015)ではPromiseという仕組みが導入されました。

Promiseの基本概念

  • ① Promiseはコンストラクタ関数です const p = new Promise()のようにしてPromiseインスタンスを作成でき、これは非同期処理を表します。
  • ② Promise.prototypeには**.then()**メソッドが含まれています 各Promiseインスタンスは、.then()メソッドにアクセスできます。例:p.then()
  • .then()メソッドは成功・失敗時のコールバックを事前に設定するために使われます p.then(successCallback, errorCallback) p.then(result => { }, error => { }) 成功コールバックは必須で、失敗コールバックは省略可能です。

then-fsを使ってファイルを読み込む

Node.jsの標準モジュールfsはコールバック形式でのみファイル読み込みに対応しており、Promise形式ではありません。そのため、npm i then-fsコマンドで第三者ライブラリをインストールし、Promiseベースのファイル操作を行う必要があります。

  • then-fsのreadFile()は非同期でファイルを読み込み、戻り値としてPromiseオブジェクトを返します。このPromiseに対して.then()メソッドを呼び出して処理を記述できます。例:
import thenFs from 'then-fs';
thenFs.readFile('./files/1.txt', 'utf8')
  .then(r1 => { console.log(r1); }, err1 => { console.log(err1.message); });
thenFs.readFile('./files/2.txt', 'utf8')
  .then(r2 => { console.log(r2); }, err2 => { console.log(err2.message); });
thenFs.readFile('./files/3.txt', 'utf8')
  .then(r3 => { console.log(r3); }, err3 => { console.log(err3.message); });

前の.then()が新しいPromiseを返す場合、次の.then()でそれを処理できます。これにより、コールバック地獄を回避できます。

Promiseによる連続したファイル読み込み

// Promiseのチェーンにより、コールバック地獄を解消
thenFs.readFile('./files/1.txt', 'utf8')
  .then((r1) => {
    console.log(r1);
    return thenFs.readFile('./files/2.txt', 'utf8');
  })
  .then((r2) => {
    console.log(r2);
    return thenFs.readFile('./files/3.txt', 'utf8');
  })
  .then((r3) => {
    console.log(r3);
  });

エラー処理として.catchを使用

Promiseの連鎖の中でエラーが発生した場合、Promise.prototype.catchメソッドでキャッチできます。

  • 前のエラーが後の.then()に影響しないようにするには、.catch()を先に呼び出すべきです。
thenFs.readFile('./files/11.txt', 'utf8')
  .catch(err => {
    console.log(err.message);
    // エラーを処理したため、後続の.thenが実行される
  })
  .then(r1 => {
    console.log(r1); // undefinedが出力される
    return thenFs.readFile('./files/2.txt', 'utf8');
  })
  .then(r2 => {
    console.log(r2); // 222が出力される
    return thenFs.readFile('./files/3.txt', 'utf8');
  })
  .then(r3 => {
    console.log(r3); // 333が出力される
  });

Promise.all()メソッド

Promise.all()は複数の非同期処理を並列に実行し、すべての処理が完了した後に次の.then()を実行します(待機機構)。

const promiseArr = [
  thenFs.readFile('./files/1.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/3.txt', 'utf8'),
];

Promise.all(promiseArr)
  .then(([r1, r2, r3]) => {
    console.log(r1, r2, r3); // 全てのファイルが読み込まれた結果
  })
  .catch(err => {
    console.log(err.message); // エラーがあればここに到達
  });

Promise.race()メソッド

Promise.race()も複数の非同期処理を並列に実行しますが、最初に完了した処理のみが次の.then()を実行します(競争機構)。

const promiseArr = [
  thenFs.readFile('./files/1.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/3.txt', 'utf8'),
];

Promise.race(promiseArr)
  .then(result => {
    console.log(result); // 最初に完了した結果が表示される
  })
  .catch(err => {
    console.log(err.message);
  });

Promiseを使ったファイル読み込み関数の作成

非同期処理を定義するには、new Promise()の引数にコールバック関数を渡します。この関数内で非同期処理を行い、成功または失敗時にそれぞれresolveまたはrejectを呼び出します。

function loadFile(fpath) {
  return new Promise((resolve, reject) => {
    fs.readFile(fpath, 'utf8', (err, dataStr) => {
      if (err) return reject(err);
      resolve(dataStr);
    });
  });
}

// 使用例:
loadFile('./files/1.txt').then(
  success => { console.log(success); },
  failure => { console.log(failure.message); }
);

タグ: javascript Promise 非同期処理 then-fs Node.js

5月20日 03:12 投稿