JavaScriptのPromiseとは?入門用に分かりやすく解説!

皆さん、こんにちは。

JavaScriptでコーディングをしていると、どうしても避けられないのが「非同期処理」です。ここではJavaScriptで非同期処理を行う際に用いる「Promise」について入門者用に分かりやすく解説します。

▼本記事で学べる内容

  • 非同期処理や同期処理や逐次処理とは何か
  • Promiseとは何か
  • Promiseの3つの基本

非同期処理や同期処理や逐次処理とは?

「非同期処理」や「同期処理」や「逐次処理」という言葉を初めて聞く方は多いでしょう。

まず逐次処理とは、順番に処理を行っていくことです。スーパーでレジが一つしかない店舗では、すべての客を一つのレジで捌きます。待っている人数によっては「人の流れが悪くなってしまう」ことでしょう。このように、逐次処理は待ち時間を減らしたい場合にネックになります。

次に「非同期処理」です。逐次処理の反対で、「複数のレジがあるスーパー」に該当します。複数のレジがあるので、客の回転が速くなります。しかし、人的資源や設備投資がその分多く必要ですので、スーパーによっては人件費がかさんだり、シフト調整の問題が噴出します。このように、非同期処理を上手く回すには、色々と工夫が必要です。

最後に「同期処理」です。よく誤解されるのが「逐次処理=同期処理」であるという認識です。確かに逐次処理は同期処理の一部ですが、厳密にはイコールではありません。同期処理はスーパーのシャッター後(閉店後)の処理を想像すると適切でしょう。レジが一つであっても、レジが複数であっても、この処理は毎日必要です。次の処理(明日)のために、同期(売り上げを集計)するわけです。

このような「逐次処理」や「非同期処理」に「同期処理」をJavaScriptで簡単に行うための仕組みが、今回解説する「Promise」です。

Promiseとは?

Promise(プロミス)とは、JavaScriptで非同期処理を実現するための機能です(参考*1)。端的に言えば、Promiseは日本語で「約束事」と訳されるように、「非同期処理を記述する際の決まり事」です。

この決まり事を実現するために、Promiseオブジェクトを生成し、そのオブジェクトを介して非同期処理や同期処理を実現します。

また現在Internet Explorerを除くPC/スマートフォンのほとんど全てのモダンブラウザで基本的な機能が動作する点もPromiseの特徴といえます(参考*1)。

Promiseの3つの基本

さて、本記事を通して覚えてほしいPromiseの基本は3つあります。

  • 非同期処理の定義
  • 同期処理の定義
  • 例外処理の定義

Promiseの基本1 – 非同期処理の定義

まず覚えてほしいのは「複数の非同期処理を同時に実行する際の書き方」です。目に見えている処理とは別に、裏で大量の画像をロードしておくようなシーンを想像すると分かりやすいでしょう。

// 処理1をp1として定義
const p1 = new Promise((resolve, reject)=>{
  //  : 何らかの処理
  resolve(“resolved!”); // 成功ならresolve関数を実行

  /*
  reject(“rejected!”); // 失敗ならreject関数を実行
  */
});

// 処理2をp2として定義
const p2 = new Promise(resolve=>{
  // 何らかの処理を実施
  // 失敗時に処理が不要ならrejectを省略可能
  resolve(“resolved!”);
});

// p1とp2の完了を待つ
Promise.all([p1,p2]).then(()=>{
  // p1とp2完了後の処理を記述
  return new Promise(resolve=>{
    // 何らかの処理を実施
    resolve(“resolved!”);
  });
});

この例ではp1とp2の処理を記述し、その両方を非同期に同時実行し、両方の処理の完了を待ち、完了後に別の処理を行います。

p1やp2に該当する処理の内容はPromiseを生成(new)し、それに処理を行う関数を与えて指定します。その処理を行う関数は、引数としてresolveとrejectという関数を受け取ります。それは「resolveを呼び出すと処理成功」として扱われ、逆に「rejectを呼び出すと処理失敗」として扱われます。なお処理失敗時に「改めて処理する必要がない場合」、rejectを引数自体から省略しても構いません。

またこの例では定数p1とp2にPromiseを生成して格納していますが、配列に代入していっても構いません。実際に非同期処理の完了を待つPromise.all関数は、Promiseの配列を受け取るためです。

さて、ここで忘れてはならないことがあります。それは実際問題、複数の非同期処理を行った後、「最終的には全ての非同期処理の完了を待たなければならない点」です。

これは当たり前のことですが、ローディングの途中で次の処理に行ってしまい、ロードされていない要素にアクセスしてしまっては困るからです。それには、次に示す「then」関数を用いて同期を取る必要があります。

Promiseの基本2 – 同期処理の定義

次に覚えてほしいのが「同期処理の書き方」です。たとえ非同期処理といえども、最終的な同期は必ず必要になります。また、場合によっては前処理や後処理が必要な場合もあります。

Promiseはメソッドチェーンを実装しています。そのため、Promiseでは同期したい処理を「.」(ドット演算子)と「then」関数(同期用関数)でチェーンのようにつないでいくだけです。以下はthen関数で逐次処理を行う例です。

new Promise((resolve, reject)=>{
  resolve("A resolved!");
}).then(()=>{
  return new Promise(resolve=>{
    resolve("B resolved!");
  });
}).then(()=>{
  return new Promise(resolve=>{
    resolve("C resolved!");
  });
});

ここで注目してほしい点は2つです。

まず、複数の処理を「then」関数で数珠つなぎにしていることです。これによって1つの処理完了後、次のthen関数に処理が移ることで逐次処理を実現しています。

次に注目してほしいのがthen関数の戻り値が「return new Promise(~)」となっている点です。この記述を行うことで「then関数のネストが解消されている」点が重要です。

ここでいう「then関数のネスト」とは、以下のような記述方法です。

new Promise((resolve, reject)=>{
  resolve("A resolved!");
}).then(()=>{
  new Promise(resolve=>{
    resolve("B resolved!");
  }).then(()=>{
    new Promise(resolve=>{
      resolve("C resolved!");
    })
  })
});

これでは同期する処理が増えれば増えるほどネストが深くなっていき、コーディングの手間が増えてしまいます。また、ネストの深さが処理によって変わってしまうため、処理の順序を後から入れ替えることが難しくなってしまいます。

対してreturnでPromiseを返却すれば、基本的に同じ形(同レベルのネスト)でコーディングができるため修正は容易です。

このようにreturnでPromiseを返却してチェーンを記述できる理由は、then関数が引数で指定された関数を呼び出しつつ、returnで指定された「Promise」に処理を渡しているからです。

Promiseの基本3 – 例外処理

最後に覚えてほしいのが、Promiseの例外処理です。これにはメソッドチェーンに「catch」関数を記述することで実現できます。

例えば以下のようなコードを実行すると、即座に例外が発生します。

new Promise(reject=>{
  reject("reject!");
});

この例外を通常のように「try~catch」で捕捉することもできますが、Promiseなら「catch」関数を用いることでより簡単に例外処理を記述できます。

例えば、例外発生時にコンソール・ログへメッセージを出力する場合、以下のように1行の記述を変更するだけで対応ができます。

new Promise(reject=>{
  reject("reject!");
}).catch(value=>console.log(value));

また、「catch」関数をどの位置に記述するかによって、どの位置で例外を取得するか調整したり、はたまた全ての例外を一括で処理したりすることも可能です。

最後に

今回解説した「Promise」は、非同期処理や同期処理の重要な技術です。

しかし、現在では今回の内容は少し古い内容と言えます。なぜなら現在はES2016以降がデファクトスタンダードとなっており、Promiseをベースに、より便利な記述ができる「async」構文や「await」構文などが登場しているためです。

そこで次回は今回の内容に続いて、「async/await」について解説します。よろしければ次回の記事をお待ちいただければ幸いです。




エンジニア募集中

株式会社キャパでは中途エンジニアの募集をしています。
私たちと一緒に働きませんか?
募集概要について詳しくは


◆参考文献

参考*1:Promise
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

関連記事一覧