非同期なコールバック関数の終了を待つ

JavaScript

非同期な関数の終了を待つ・Promiseクラス

非同期な関数の終了を待つ方法を紹介する。 代表的な非同期関数は、指定時間後に処理を起動する setTimeout、setInterval、ファイル入出力を行う fs.readFile、fs.writeFileなどがある。 これらの関数は、関数を呼び出した後、処理の終了(一般的にはコールバック処理の終了)を待たずに次のステップに制御が移る。次の例を参照。
fs.readFile関数は指定したパスのファイルを読み込み、読み込んだデータはコールバックの中で処理する。プログラムの制御はその処理の終了を待たずに次のコードに移る。
読み込んだデータの処理はコールバックの中で行う必要があるが、例えば複数のファイル入出力処理(fs関数)を直列的に実行するような場合、コールバックのネストにより可読性の悪いコードになってしまう。
非同期な関数の処理の終了を待って次のステップに移るようにするには、Promiseクラスの機能を利用する。
代表的な使用法のパターンを fs.read関数を例にとって紹介したい。

Promiseオブジェクト

Promiseオブジェクトを利用すれば、非同期処理を同期処理として実行することができる。
Promiseオブジェクトを作成し、Promiseクラスのコンストラクタ関数に中に非同期処理を記述する。関数は二つのメソッドを引数に持ち、非同期処理が正常終了した場合は 最初のメソッド(resolve)を、異常終了した場合は 二番目のメソッド(reject)を実行する。この時点で待ち状態が解消される。
エラー系の処理を省略できるのであれば、引数はひとつ(resolve)だけでかまわない。
resolveメソッドを実行すると Promiseオブジェクトの thenメソッドが起動し、引数に resolveメソッドの引数を得られるので、これをもとに正常系の処理を行う。
rejectメソッドを実行すると catchメソッドが起動し、引数に rejectメソッドの引数を得られるので、これをもとにエラー系の処理を行う。
次のようにすれば Promiseオブジェクトの作成と実行を同時に行うことができる。

async / await 関数パターン

Promaiseオブジェクトを戻り値にした関数を await演算子を付与して実行すると、実際の戻り値として resolve関数の引数、つまり非同期関数の実行結果を得ることができる。この方式にすれば、一般的な関数呼び出しと同じ形式で、非同期関数を呼び出すことができる。
なお、この関数呼び出しを行う処理は、async関数の中で行われる必要がある。async関数自体は非同期な処理なので、呼び出しが戻る前に、次のステップに移る。例では、callPromise関数が終了する前に、直後の console.log("1. Next to callPromise")が実行されている。
関数呼び出しを try...catch文で囲めば、処理がエラーで rejectメソッドを実行したとき、その引数の値を例外オブジェクトとして補足することができる。
Promaiseオブジェクトの定義を別関数にしたもの。処理の内容は上記と全く同じである。関数の戻り値の取得とその操作が近いのでわかりやすいかもしれない。
実行結果をステータスコードとして関数の呼び元に返す例である。rejectメソッドの引数には errorオブジェクト以外、どのような形式の値を指定してもかまわない。この値は catch文の引数に渡される。
async / await のネスト
非同期(async)関数からの戻り値を取得したい場合は、非同期関数の終了を awaitで待てば良いが、そのためには自身が非同期関数になる必要がある。下記の例では、callPromise関数の戻り値を得るために、main関数を非同期として定義している。
上記の変形(いくらか簡潔)