学習項目の整理
①基本概念-promiseとは?☑
②promiseチェーン-値の伝播・エラーの伝播を構造化する設計☑
③並列処理とそのユースケース☑
④async-awaitでの代用☑
①基本概念
1.promiseとは何か?promiseの定義とは?
- promise誕生の背景:
非同期処理完了に伴う処理はこれまでコールバックを用いて行われていた。しかしコールバックでは複雑な処理を扱いたい際ネストが深く読みづらかった。
- promiseとは?:
非同期処理の状態「処理中」「成功」「失敗」を表すオブジェクト。
.then()や.chatch()といったメソッドによって非同期処理の完了に伴う処理を定義できる。
2.promiseの状態とは?
Pending状態: Promise生成後、非同期処理の完了前の状態。
Fulfilled状態: 非同期処理が成功し、結果の値が得られた状態。.then,
Rejected状態: 非同期処理が失敗し、エラーが発生した状態。.catch
不変性: 一度状態が確定すると、その後は変更されない。
コールバックの実行: 変更後の状態に応じたコールバック(.then, .catch)が適切なタイミングで呼ばれる(即時実行はされない)。
3.promiseの生成方法、Executor関数とは?
- 確認:promiseは非同期処理の状態を表すオブジェクト
- 生成方法:promiseコンストラクタを利用して生成する。構文は以下の通り
const promise = new Promise((resolve, reject) => {
// executor
// ここで非同期処理を実行する
// 非同期処理が成功したら、resolve(結果) を呼ぶ
// 失敗したら、reject(エラー) を呼ぶ
});
- Executor関数とは?
プロミスが生成された時点で即時実行される。
非同期処理の呼び出しもここで行われる。(処理自体はブラウザ,node.jsなどのホスト環境によって管理)
resolve と rejectという引数をとり、内部の非同期処理の成否に応じてresolveまたはrejectを呼び出すことで、プロミスオブジェクトの状態を変化させる。
なお明示的にrejectを呼ばなくともexecutor内で例外があった場合、プロミスオブジェクトはrejectが呼ばれたときと同様の挙動をする。
(resolveの場合は呼ぶ必要があることに注意)
②promisチェーン
1.基本概念と仕組み
- Promisチェーンとは
複数の非同期処理(Promise)を.thenメソッドで連結し、各Promiseが解決(または拒否)された結果を次の処理に引き継いで実行する仕組み。各 .then は新たな Promise を返す。
- 基本的な動作
各promiseの解決に伴って次の.thenや.catch内のコールバックがマイクロタスクとしてスケジューリングされる。
-
thenの基本的な挙動-then内のコールバックが返す値によって変わる
共通:どちらもプロミスを返す
単純な値⇒promiseでラップして次の.thenに渡す
promise⇒そのプロミスの解決を待って次の.thenにpromiseを渡す
- 同期・非同期の境界
非同期の意味を区別する
①非同期処理:
イベントループやタスクキュー(マイクロタスクキューやマクロタスクキュー)を活用して、メインスレッドの実行を中断することなく、外部処理や待機時間のある操作の結果を後から処理する仕組み全体。ある処理の完了を待たずに他の処理が行われる複数の処理を効率的に実行するための仕組みのこと。
②非同期実行:(promise/webAPIの完了に伴うコールバックなど)
webAPIのコールバック/Promise.thenのコールバックなどはwebAPIやjsエンジン内部の機構によってマイクロタスクキュー、マクロタスクキューにエン給され、実行のタイミングが遅延される「非同期スケジューリング」。(これらの実行タイミング制御フローの概念をイベントループと呼ぶ)
③ネットワークリクエストなどの即時的に結果が得られない処理:
自分はこれまでこの処理のみを非同期処理と呼んでいたが調べていくうちに何か違う気がしてきた。
- 登録されるタスクキュー
Promiseが解決された結果の.then()
のコールバックは常にマイクロタスクキューに登録される - 重要なポイント
タスクキューへのコールバックの追加はwebAPIが絡んでいなくとも起こりうる
同期コード/非同期コード
①コールスタックに直接積まれる⇒同期コード(コードの上から順にみて即時実行)
②他のキューを経由する⇒非同期コード(非同期実行)
.thenのコールバックは渡されるプロミスの解決時にマイクロタスクキューに入れられるため必ず非同期コードとなる。
つまり渡されるプロミスが解決したからと言って.thenは即時に実行されるわけではない。現在の同期処理の終了を待つ必要がある。
console.log("1: 同期処理開始");
new Promise((resolve, reject) => {
console.log("2: Promise executor 内(同期)");
resolve("結果");
}).then(result => {
console.log("3: .then() コールバック内(非同期)", result);
});
console.log("4: 同期処理終了");
new Promise((resolve, reject) => {
console.log("5: Promise executor 内(同期)");
resolve("結果");
}).then(result => {
console.log("6: .then() コールバック内(非同期)", result);
});
仮にコールスタックに直接積まれる同期コードである4にたどり着く前にpromiseが解決されたとしても3は親のpromise解決時にマイクロタスクキューに送られる非同期コードであるため3が先に実行されることはない。
重要なポイント
- executorは同期コード。promiseのインスタンス化と同時に中の処理が呼ばれる。
- .then()内のコールバックは必ず親のプロミスの解決を待つ非同期コードとなる事。
- 親のプロミスがバックグラウンドとの処理があるかどうかは関係なく.thenのコールバックはキューに追加される
2.値の伝播とチェーンの連結
- .thenメソッド返り値のルール
自動ラッピング:
コールバックで非Promise値が返された場合、内部的に Promise.resolve() によってラップされる。
(エラー発生時はPromise.reject)
重要なポイント:.
.thenコールバックが単純な値を返す場合もラッピングされpromiseオブジェクトとなる。
後続の.thenのコールバックは必ずマイクロタスクキューへと追加される。
- .thenのコールバックが受け取るデータのルール
前の.thenコールバックが返した値、もしくは解決値を受け取る
重要なポイント:
プロミスそのものではなく値を受け取る
- promiseチェーンの継続
途中の.thenコールバックがreturnを行わない場合も次の.thenコールバックには値がundifinedとしてわたされチェーンは継続する
- catch後のリカバリー
catchがエラーをスローせず正常な値を返せば後続の.thenで正常な値として処理が継続される
- promiseのネストと平坦化
ネストが起こっている場合外側のpromiseは内側のpromiseの解決を待ち内側のpromiseの状態や解決値に従ったpromiseを返す
3. エラー伝播
- エラーの伝播メカニズム
チェーン内でエラーが発生した場合、以降の .then がスキップされ、最初に現れる .catch が実行される
then の第2引数でもエラーを捕捉可能だが、一般的には .catch を使う
③ 並列実行と制御手法
知識不足故Reactを利用するうえでの有効性を感じられませんでした。(同じコンポーネント内で複数のデータ取得APIを呼び出すケースが想定できませんでした)もしよろしければだれか教えていただけると幸いです。。。
④aync/awaitでの代用
💡上記「ync/awaitでの代用」に対する修正:async/awaitはpromiseとは異なる非同期処理方法を提供するわけではなく、より直感的な記述を行うための“糖衣構文”です。内部処理としてPromise.resolve(式).then(…)
のような処理に変換されます。
代用ではなく記述方式の一つです。
1.基本動作
-
async
キーワードを付けると、その関数は常にPromiseを返す - promiseを返す処理の前にawaitを利用するとasync関数内の処理を一時停止できる。(ここがpromiseとの有効な違いといえると感じた)
- await以後のコードは.thenのコールバックと同様にプロミスの解決に伴ってマイクロタスクキューへ送られる。
- async関数もPromiseと同様に「平坦化(flattening)」される
- Promiseが拒否された場合、
await
は例外を投げる - 例外を投げられたもしくはreject状態のpromisを内部で受け取ったasync関数はcatchしない限りreject状態のpromiseとなる
- catchするとresolve状態のpromisになる
正直あまりpromisと比較したときのasync-awaitの強みが理解しきれていません。
2ネストと値の伝播
- 自動的なpromiseラップ
async関数内でreturnした値は、自動的にPromise.resolve()でラップされます。つまり、単に値を返しても、その呼び出し側ではPromiseとして受け取ります。
- Promiseの平坦化:
async関数の戻り値が既にPromiseの場合、そのPromiseは「平坦化」されます。つまり、内部で返されたPromiseの状態(fulfilledまたはrejected)と解決値が、async関数の返すPromiseに反映されます。
💡修正:
平坦化を行うのは.then()メソッドの機能でありasync-awaitだからこのような挙動になっている名ではない
- 例外の自動ラップ:
async関数内で例外が発生すると、その例外は自動的にPromise.reject()でラップされ、呼び出し側でcatchできる形に伝播されます。
- awaitによる例外伝播:
awaitで待っているPromiseが拒否(reject)された場合、そのエラーはawaitの位置で例外として投げられ、通常のtry/catch構文で捕捉可能です。
- awaitの基本動作:
awaitは、指定したPromiseが解決されると、その解決値を返します。結果として、await式はそのPromiseの解決値に置き換えられ、次の処理へ伝播します。