Promiseを少しでも理解できるようになるために

19150 ワード

JavaScriptで非同期処理を実行する時に必ず通らないといけない道...それはpromiseです。

昨今のフロントエンド開発では非同期処理とはうまく付き合っていかなければならないのですが、
「Promise...とは...?」となっている人にとってpromiseを理解するのは難しいです。
かくいう私もだいぶ苦戦してやっと使えるようになった側なので、理解するのは難しいと思っています。
この記事が少しでも役に立てれば幸いです。

Promiseとは

プロミス (Promise) は、作成された時点では分からなくてもよい値へのプロキシーです。非同期のアクションの成功値または失敗理由にハンドラーを結びつけることができます。これにより、非同期メソッドは結果の値を返す代わりに、未来のある時点で値を提供するプロミスを返すことで、同期メソッドと同じように値を返すことができるようになります。

MDNより抜粋:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

Promiseで大事なこと

  • Promiseには状態があります。以下の3パターンです。

1.pending

  • 初期状態で待機しています。成功も失敗もしていません。

2.fulfilled

  • 処理が成功していて、かつ完了したことを意味します。

3.reject

  • 処理が失敗したことを意味します。

Promiseの基本構文

new Promise((resolve, reject) => {
  // resolve() ...処理が成功した場合
  // reject() ...処理が失敗した場合
});

Promiseで遊んでみる

早速、Promiseで非同期関数を作って遊んでみましょう。
asyncFunctionという関数で、状態がresolveした時に文字列を返す関数を作って見ました。

const asyncFunction = () => {
  return new Promise((resolve) => {
    resolve("async Hello");
  });
};

さて、この関数をこのまま実行したらどうなるでしょうか。
成功も失敗もしていません。Promiseオブジェクトが返されました。

Promise { 'async Hello' }

ここからが大事なのですが、Promiseの結果を得るには、ハンドラを使って値を返す必要があります。
結果を得たいのはresolveなのでthen()を使って値を返します。

asyncFunction().then((value) => console.log(value));
// 実行結果: async Hello

今度はrejectを返すバージョンを作ってみます。
rejectの結果を値として返す場合はcatch()を使って値を返します。

const asyncFunction = () => {
  return new Promise((resolve, reject) => {
    reject("...reject");
  });
};
asyncFunction().catch((value) => console.log(value));
// 実行結果: ...reject

rejectの結果をエラーハンドリングをする

関数の引数に、配列と特定の値を入れて、配列内に特定の値が含まれているかどうかを判定する非同期関数を作って見ます。rejectが返ってくる時はエラーハンドリングを加えます。

const existValue = (array, value) => {
  return new Promise((resolve) => {
    if (array.includes(value)) return resolve(`${value}は配列に含まれています`);
    throw new Error(`${value}は配列に含まれていません`);
  });
};
existValue([1, 2, 3], 4)
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    if (error instanceof Error) {
      console.log(error.message);
    }
  });
  // 実行結果: 4は配列に含まれていません

ところでなぜ、rejectを書いていないのに、エラーハンドリングされたのでしょうか?
.then()ハンドラの中でthrowしたため、自動的にrejectと判断されたのです。

なので、rejectを明記した場合は以下のように書けて、同様の意味になります。

return reject(new Error(`${value}は配列に含まれていません`));

どうですか、Promise面白いですよね?

aysnc / await

Promiseが理解できたら次はasync / awaitについて簡単に触れたいと思います。
この構文も昨今のフロントエンドでよーく使われているので、見たことある人も多いと思います。

async / awaitpromiseを併用して、
先ほど、「rejectの結果をエラーハンドリングをする」で書いた関数を書き換えて見ましょう。

メソッドチェーンで制御していた部分はtry ~ catchを使って制御します。
イメージとしてメソッドチェーンの.then()で処理を待っていた部分にawaitを使って、非同期処理の実行を待ちます。

const existValue = async (array, value) => {
  return await new Promise((resolve) => {
    if (array.includes(value)) return resolve(`${value}は配列に含まれています`);
    throw new Error(`${value}は配列に含まれていません`);
  });
};

(async () => {
  try {
    const result = await existValue([1, 2, 3], 4);
    console.log(result);
  } catch (error) {
    if (error instanceof Error) {
      console.log(error.message);
    }
  }
})();

// 実行結果: 4は配列に含まれていません

ということでPromiseについての説明でした。
Promiseはまだまだ奥深いのですが、理解できないと悩んでいる方のお力になれたら幸いです。