2022年10月6日木曜日

JavaScriptのPromiseを理解する

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

1. Promiseは何のため?

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

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

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

//Fetch APIを使う場合
fetch(url)	//urlにアクセスして
 	.then(response => response.json()) //responseを解釈し
	.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つの関数を登録する。
// funcAは処理を実行、funcBはエラーを処理
fetch(url).then(funcA, funcB) 
//または
fetch(url)
	.then(funcA)
	.catch(funcB)

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

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

Promiseは、組み込みのPromiseを返す関数以外にPromiseコンストラクタを使うことでPromiseを返す関数を作成することができる。
これを利用してミリ秒を引数として受け取り、その値を返す関数を書くと次のようになる。
//durationを引数として受け取り
//durationミリ秒後にPromiseをduratoinで満たす
function wait(duration){
    return new Promise((resolve, reject)=>{
        setTimeout(() => {resolve(duration)}, duration);
    })
}


//start→4000→setTimeoutの順でコンソールに表示される 
console.log("Start");
setTimeout(() => {console.log("setTimeout")}, 5000);
wait(4000).then(time => {console.log(time)});

//start→setTimeout→4000の順でコンソールに表示される 
console.log("Start");
setTimeout(() => {console.log("setTimeout")}, 3000);
wait(4000).then(time => {console.log(time)});

5.まとめ

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