2022年10月6日木曜日

JavaScriptのPromiseを理解する

 JavaScriptの非同期処理がよくわからない、PromiseってなんぞってなったのでPromiseについてまとめてみた。

1. Promiseは何のため?

A. Promiseは非同期処理のため
これが簡潔な答えになる。
JavaScriptでは、あることをしながら別のことをするということができない。つまり、ウェブサイトのデータをダウンロードしながら画面にスピナーを表示するといったことができない。
これを解決するのが非同期処理になる。JavaScriptでは、Callbackやイベント駆動という方法がある。その新しい方法がPromiseになる。

2. Promiseのコードはどう使うか?

JavaScriptでは.then()という形で呼び出すことになる。

  1. //Fetch APIを使う場合
  2. fetch(url) //urlにアクセスして
  3. .then(response => response.json()) //responseを解釈し
  4. .then(data => console.log(data)); //データを表示する
(https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetchから引用)

このケースではFetch APIを取得し、対象のURLから情報を受け取り、JSONとして解釈して、そのデータを表示するということを行っている。
言い換えれば、.then()内に与えられる関数(ここではアロー関数)を呼び出して、fetch()の戻り値→response.json()の戻り値を順に与えて処理を行っている。


3. Promiseはどのような仕組みで動くか?

Promiseは、ある関数に一時的にPromiseというものを返し、処理を続行する。このPromiseが満たされるか、失敗するか、解決することでその関数は、処理を解決する。
Promiseを返す関数には2つのコールバック関数を登録することができる。1番目のコールバック関数が呼び出されたときにPromiseは満たされ、2番目のコールバック関数が呼び出されたときにPromiseは失敗する。
つまり、以下のようなコードで2つの関数を登録する。
  1. // funcAは処理を実行、funcBはエラーを処理
  2. fetch(url).then(funcA, funcB)
  3. //または
  4. fetch(url)
  5. .then(funcA)
  6. .catch(funcB)

そして、解決するということは、いわばPromiseを返す関数がPromiseを受け取り、処理がペンディングされている状態になる。そして、あるPromiseがPromise以外の値で満たされたとき、連鎖してPromiseが満たされ、あるPromiseが失敗すれば、同じ理由で連鎖してPromiseが失敗する。
つまり、コードで表すと以下のようになる。
  1. //HTTPリクエストを行い、最初にPromiseを返し、
  2. //リクエストが返ってきたら、Promiseはresponseオブジェクトで満たされる
  3. fetch(url)
  4. //HTTPリクエストのPromiseが満たされるまで
  5. //fetch()から返ってきたPromiseで解決される
  6. //HTTPリクエストのPromiseが満たされると
  7. //response.jsonが返すPromiseは満たされる
  8. .then(response => response.json())
  9. //response.json()の受け取ったPromiseが満たされるまで
  10. //最初のthen()が返すPromiseで解決される
  11. .then(data => displayData(data));

4. Promiseの動きを見るためのコード

Promiseは、組み込みのPromiseを返す関数以外にPromiseコンストラクタを使うことでPromiseを返す関数を作成することができる。
これを利用してミリ秒を引数として受け取り、その値を返す関数を書くと次のようになる。
  1. //durationを引数として受け取り
  2. //durationミリ秒後にPromiseをduratoinで満たす
  3. function wait(duration){
  4. return new Promise((resolve, reject)=>{
  5. setTimeout(() => {resolve(duration)}, duration);
  6. })
  7. }
  8. //start→4000→setTimeoutの順でコンソールに表示される
  9. console.log("Start");
  10. setTimeout(() => {console.log("setTimeout")}, 5000);
  11. wait(4000).then(time => {console.log(time)});
  12. //start→setTimeout→4000の順でコンソールに表示される
  13. console.log("Start");
  14. setTimeout(() => {console.log("setTimeout")}, 3000);
  15. wait(4000).then(time => {console.log(time)});

5.まとめ

Promiseは仮の値を関数に与えることで処理したことにして(処理したとみなし)、次の処理に進む。そのうえで、最初のPromiseが満たされたとき、連鎖的にPromiseが満たされて結果が返ってくる。