Promiseをちゃんと理解したい②

2020/12/06

非同期処理をちゃんと理解するぞ!
ということで,昨日(Promiseをちゃんと理解したい①)に引き続き今日もPromiseやっていきます。

Promiseを使って非同期処理を順番に処理する

thenメソッドやcatchメソッドは常に新しいPromiseインスタンスを作成して返すという仕様を利用して,非同期処理を順番に処理をしていきます。
thenメソッドをつないでいくことで,順番に登録した順でコールバック関数が呼ばれ実行されます。

Promise.resolve()
  .then(() => {
    console.log('1つ目の非同期処理');
  })
  .then(() => {
    console.log('2つ目の非同期処理');
  });

成功時と失敗時

Promiseを返す関数が成功(resolve)した場合はthenメソッドで登録した処理が呼び出され,失敗(rejected)した場合はcatchメソッドで登録した処理が呼び出されます。
失敗した場合は,その時点でcatchメソッドに登録した処理が呼び出され,thenメソッドが続いていてもスキップされます。

Promise.resolve()
  .then(() => {
    console.log('1つ目の非同期処理');
  })
  .then(() => {
    console.log('2つ目の非同期処理');
    throw new Error('Error');
  })
  .then(() => {
    console.log('3つ目の非同期処理'); // スキップされる
  })
  .catch(err => console.log(err.message));
// 実行結果
// 1つ目の非同期処理
// 2つ目の非同期処理
// Error

catchメソッドはFulfilledでPromiseインスタンスを返す

catchメソッドはFulfilled状態のPromiseインスタンスを作成して返すので,thenメソッドやcatchメソッドをつなげることができます。

Promise.resolve()
  .then(() => {
    console.log('1つ目の非同期処理');
    throw new Error('Error');
  })
  .catch(err => console.log(err.message))
  .then(() => {
    console.log('catchメソッドの後に呼び出される');
  });
// 実行結果
// 1つ目の非同期処理
// Error
// catchメソッドの後に呼び出される

コールバック関数に引数を渡す

thenメソッド,catchメソッドのコールバック関数内でreturnすると,次のthenメソッド,catchメソッドのコールバック関数の引数にreturnした値を渡すことができる。

Promise.resolve('Promiseインスタンスから渡された値')
  .then(value => {
    console.log(value);
    return '1つ目の処理から与えられた値';
  })
  .then(value => {
    console.log(value);
    throw new Error('エラー発生');
  })
  .catch(err => {
    console.log(err.message);
    return err.message;
  })
  .then(value => console.log('catchメソッドから渡された値:' + value));
// 実行結果
// Promiseインスタンスから渡された値
// 1つ目の処理から与えられた値
// エラー発生
// catchメソッドから渡された値:エラー発生

成功時でも失敗時でも行う処理

成功した場合も失敗した場合も同様の処理を最後に行いたい場合にはfinallyメソッドを使います。

// 成功した場合
Promise.resolve()
  .then(() => console.log('成功'))
  .catch(() => console.log('失敗'))
  .finally(() => console.log('処理が完了しました'));
// 実行結果
// 成功
// 処理が完了しました

// 失敗した場合
Promise.reject()
  .then(() => console.log('成功'))
  .catch(() => console.log('失敗'))
  .finally(() => console.log('処理が完了しました'));
// 実行結果
// 失敗
// 処理が完了しました

複数のPromiseをまとめる

複数の非同期処理で実行の順番は気にしないが,全て実行が完了してから次の処理をしたい場合,Promise.allメソッドを使います。
Promise.allメソッドの引数には,Promiseインスタンスの配列が入り,全てのPromiseインスタンスがFulfilled状態になったときに返り値のPromiseインスタンスもFulfilledとなります。
逆に,一つでもRejectedとなると,返り値のPromiseインスタンスもRejectedとなります。

// 成功する非同期処理
const successAsyncFunc = (value) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, 1000);
  });
};

// 失敗する非同期処理
const failedAsyncFunc = (value) => {
  return new Promise((reject) => {
    console.log(value);;
    throw new Error('失敗しました');
  });
};

const first = successAsyncFunc('1つめのPromise');
const second = successAsyncFunc('2つめのPromise');
const third = successAsyncFunc('3つめのPromise');
const fail = failedAsyncFunc('失敗するPromise');

// 全て成功した場合
Promise.all([first, second, third])
  .then(values => console.log(values))
  .catch(err => console.log(err.message));
// 実行結果
// [ '1つめのPromise', '2つめのPromise', '3つめのPromise' ]

// 失敗するPromiseが入っている場合
Promise.all([first, second, fail])
  .then(values => console.log(values))
  .catch(err => console.log(err.message));
// 実行結果
// 失敗するPromise
// 失敗しました

非同期のタイムアウト

Promise.raceメソッドを使うことでタイムアウト処理を実装することができます。
Promise.raceメソッドは配列で与えられたPromiseインスタンスのうちどれか一つでも完了した時点で次の処理を実行します。

// timeoutMsミリ秒後にタイムアウト
const timeout = (timeoutMs) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error(`${timeoutMs}ミリ秒経過のためタイムアウトしました。`));
    }, timeoutMs);
  });
};

// 実行時間がexecTimeミリ秒の非同期処理
const asyncFunc = (execTime) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`実行時間: ${execTime}`);
    }, execTime);
  });
};

// タイムアウトしない場合
Promise.race([
  timeout(500),
  asyncFunc(200)
])
  .then(response => console.log(response))
  .catch(err => console.log(err.message));
// 実行結果
// 実行時間: 200

// タイムアウトする場合
Promise.race([
  timeout(500),
  asyncFunc(700)
])
  .then(response => console.log(response))
  .catch(err => console.log(err.message));
// 実行結果
// 500ミリ秒経過のためタイムアウトしました。

まとめ

コールバックの非同期処理と比べるとだいぶわかりやすいし,見やすいです。
Promiseに対して身構えちゃっていましたが,だいぶ理解が進みました。
残るはasync/awaitを使用した非同期処理です。

それではまた明日。

参考


書いた人: こへ
音楽と漫画と読書とアニメとスノボが好き。多趣味でいろんなことに興味有ります。 誰しもが一度は使った事があるもののIoT開発をしてます。
Twitterフォローお願いします。