🦅

非同期処理 (3): Javascript Promiseについて

2022/05/14に公開

概要

前回の記事では非同期処理のCallbackについて書きました。非同期処理をしてくれるCallback関数はCallback Hellというネストが深くなってしまう問題があり、代わりにPromiseを利用することがあります。

Promise

非同期処理に使われ、非同期処理を囲む形で利用するobjectのことです。

let promise = new Promise(function(resolve, reject) {
  // executor: 時間がかかる処理
});

まずはcallback関数の例です。

function getData(callback) {
  $.get('API URL/products/1', function (response) {
    callback(response); // サーバから受け取ったresponseをcallback()関数に渡している
  });
}

getData(function (productDatas) {
  console.log(productDatas); // responseがproductDatasに渡される
});

こちらはPromiseを使った例です。
resolveなど詳しい内容については後述します。

function getData(callback) {
  // new Promise() 追加
  return new Promise(function (resolve, reject) {
    $.get('API URL/products/1', function (response) {
      // 無事データを受け取ったらresolve()にデータを渡す
      resolve(response);
    });
  });
}

// getData()が終わったらthen()が呼ばれる
getData().then(function (productDatas) {
  // 結果の値が入る
  console.log(productDatas);  // responseがproductDatasに渡される
});

また、.then()を利用して結果が渡していくPromise Chainingの書き方が可能なため、callback HELLのようにネストが深くなることがなくなります。

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 3000);
}).then(function(result) {
  alert(result); // 1
  return result * 2;
}).then(function(result) {
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});

Promiseの三つの状態

new Promiseでプロセスを生成して終了されるまでには三つの状態を持ちます。

  • pending:待機
  • fulfilled:実施された状態
  • rejected:拒否

動作の図はこのようになります。

書き方から状態たちを見てみましょう。

Pending:待機状態

new Promise();

Fulfilled:非同期処理が完了され、Promiseの結果が返ってきた状態

new Promise(function(resolve, reject) {
  resolve();
});

Fulfilledの例

function getData() {
  return new Promise(function(resolve, reject) {
    const data = 100;
    resolve(data);
  });
}

// resolve()の結果dataをresolvedDataとして受け取っています。
getData().then(function(resolvedData) {
  console.log(resolvedData); // 100
});

Rejected:処理が失敗した時の状態

new Promise(function(resolve, reject) {
  reject();
});

rejectedの例

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}
// reject()の結果dataのErrorをerrとして受け取っています。
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

Rejected時のPromiseのエラー処理

エラーの場合reject()が呼ばれますが、Promiseのエラー処理をする方法は二つあります。
できるだけ2番目のcatchを使った方がいいです。

  1. then()の二つ目の引数を利用する。
new Promise(function(resolve, reject){
  // success -> resolve()
  // fail -> reject()
}).then(
  function(){
    console.log('OK')
  },
  function(){
    console.log('NG'). //rejectの場合、thenの中の二番目の関数が実行される
  }
)
  1. catchを利用する。
new Promise(function(resolve, reject){
  // success -> resolve()
  // fail -> reject()
}).then(
  function(){
    console.log('OK')
  }
).catch(function(){
   console.log('catch')
})

Promiseのメソッド

promise.all()

Promise objectを配列で受け取り(promiseでないものが混ざっても構わない)、それらの処理が全て終わったらその結果をまとめて返します。あるpromiseでrejectされる場合、その時点で処理は終わり、拒否の結果が帰ってきます。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// まとめて受け取る
Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});

// 結果
//  Array [3, 42, "foo"]

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Promise.allSettled()

all()と同じく処理が全て終わったら結果をまとめて返します。
返される内容が違いますが結果だけではなく、以下が配列で返ってきます。

  • Promiseオブジェクトの結果
  • status: resolveの場合fulfilled OR rejectの場合rejected
  • resolveの場合はvalue OR rejectされた場合はreason
Promise.allSettled([
  Promise.resolve(33),
  new Promise(resolve => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error('an error'))
])
.then(values => console.log(values));

// 結果
// [
//   {status: "fulfilled", value: 33},
//   {status: "fulfilled", value: 66},
//   {status: "fulfilled", value: 99},
//   {status: "rejected",  reason: Error: an error}
// ]

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

Promise.any()

最初に解決されたPromiseの結果が返ってきます。

const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [promise1, promise2, promise3];

Promise.any(promises).then((value) => console.log(value));

// 結果
// "quick"

その他にも様々なメソッドがありますのでこちらをご参考ください。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

最後に

callback hellは回避されるが、Promiseもコードが冗長されることがあり、async/awaitと組み合わせて使う場合も多いです。次回はasync / awaitについてまとめたいと思います。

Discussion