はじめに
JavaScriptのPromiseとawait(とasync)による非同期処理と待ち合わせのシンタックス入門メモです。
次の記事の姉妹記事です。 itdepends.hateblo.jp
非同期処理を使いたい背景など
JavaScriptを用いた要件で、非同期処理を織り交ぜて時間のかかる処理を並べて処理したり、IO待ちによるユーザーのストレスを軽減するためにsetTimeoutでコールバック関数で処理をさせる、というのはしばしば出くわすと思います。
自分で非同期処理を含む関数を実装することになるケースも多いでしょうが、サードパーティなどからライブラリ提供されるものを使いこなしてくださいというオーダーに従って、ライブラリのおおよその構造を理解しつつも、これを使ったビジネスロジックにできるだけ集中したいということもあるでしょう。
ここで、このサードパーティ提供の非同期処理の関数/APIは、「(君もよく知っている例の)Promise」を返してくるから簡単だよねと言われたりもします。
ともかく、非同期処理は非同期というぐらいなので、いつ応答が戻ってくるかは分かりません。Worker型で投げっぱなしで良いならかまいませんが、非同期処理が完了した際に得られるデータを使って、最終アウトプットの仕上げを行うというのが普通の要件でしょう。
例えば次の図のような例の要件は、世の中「あるある」の一例でしょうか。
非同期処理は難しい(処理モデルの理解もさることながら、言語のシンタックスのトリックとしても) → 救世主 Promiseとawait登場
やってみれば分かるのですが、手続き系のプログラミング言語では、このような例程度の非同期処理でも、スレッド(シングルスレッドの場合も含む)、プロセス、並列、並行といったことをある程度イメージしながら、テキストエディタで見たときに、上の方から処理されるような プログラムの記法で表現するのは意外に難しいです。
言語の提供される文法でどのように表記したら良いのか、思いの他パズル的な記法が求められることになります。いわゆるコールバック地獄ですね。
.... というところなのですが、2021年の現在においては、JavaScriptについても、Promiseとawait(とasync)というしかけにより、非同期処理に不慣れな方でも、少なくともawaitとPromiseの関係をつかんでおけば、先人が悩ませたこの難題から少しでも楽になれるというのが現状です*1。
ということで、Promiseをリターンしてくれるサードパーティの関数とそれを待ち合わせるawait式のほぼミニマムなプログラム記述例(冒頭の挿絵のイメージとも対応)は以下のとおりです。
await記述例
// 非同期処理Xは、Promiseをリターンする標準あるいはサードパーティ等から提供のライブラリ // → fetchなどをイメージしてください。 async function main(){ // ↑ 注:今の時点では、await式を適用するロジックを含む関数には「async」をおまじないとしてつけておく作法だと理解しておくことで良い。 const i = 処理A(); const promise = 非同期処理X(i,その他の引数など); //処理は完了していないが、Promiseのオブジェクトが戻ってきて、ロジックの評価が次ステップ以降に進んでいく。 const j = 処理B(i); const k = 処理C(j); const val = await promise; // 「await Promiseオブジェクト」という記法でこのPromiseをリターンした関数の非同期処理の実際の戻り値が得られるまで、待ち合わせするよう動作する。 //前ステップでのawait式による待ち合わせにより、処理Dは、非同期処理Xが完了するまでは処理開始されない(させない) const foo = 処理D(k,val); return foo; /* * ここでは「べからず」のみ伝えますが、 * 非同期処理Xでは、処理B、処理Cで値を変更するような変数を使わないようにしてください。 */ } main(); // node.js のv12で確認しています。
冒頭の挿絵程度であれば、たったこれだけです。
Promiseが無かった頃、await、asyncの言語組み込み以前の何がめんどくさかったのかを割愛していますので、どこが素晴らしいのか伝わりにくいですが、ともあれ、本当楽になりましたね。
なお、本記事で要点を絞る都合上、非同期処理Xでの例外発生については記載していません。
言うまでもありませんが、実際の実装では例外のハンドリングは忘れないようにしましょう。
関連記事
体系的・本格的とまではいきませんが、本記事よりももう少しPromiseを噛み砕いたり、本記事では触れていない、2つ以上の非同期処理の関数を呼び出しして並列化したものを最後にまとめあげたりという例にふれている同じテーマの別記事です。
私見では、本記事の例以外では、Promise.allを知っていれば、JavaScriptの非同期処理のおいしいところ取りができる、それだけでも応用がきくように思いますので、Promise.allについてふれています。
また、上記ではぼやかした、asyncをfunctionの前に付けている背景もそちらで述べています。
↓
<2021/1/14 時点 作成中 >
↓
参考書籍・リンク
*1:ホント先人に感謝ですね。