Chapter 38

総括 - 非同期処理のまとめ

PADAone🐕
PADAone🐕
2023.01.23に更新

このチャプターについて

大変お疲れ様でした。「非同期処理」の話はこれにて終了となります。

ここまで「非同期処理」にまつわる様々な事柄について学んできましたが、まとめのチャプターとして主な話題についての総括と振り返りを行っておきたい思います (具体的なコードを使わない非同期処理の目的や仕組みのまとめとなります)。

概念は組み合わせて理解する

「非同期 (asynchronous)、同期 (synchronous)、並列 (parallel)、並行 (concurrent)、逐次 (sequential)」といったこれらの言葉や概念は非常に紛らわしいですが、「非同期処理の全体の仕組み」を理解する上ではあますことなく 全部使います。全部使うというのは、言葉のまま、全部のタイプの概念や処理が組み合わされることで大きくくくられる「非同期処理」の仕組みが実現されるということです。

特に並列と並行の概念は厳密ではないにせよ両方同時に必要となることが非常にやっかいであり、片方だけで理解しようとしてもうまくいきません。非同期 API の実行時にはこの2つの概念を同時に成立させていることを認識しないと「非同期処理」をなぜ行うのかという目的そのものがあやふやになってしまうので注意してください。

イベントループによる並行処理

「非同期処理」を理解するための核心はイベントループであり、この機構こそが非同期にまつわるシンタックスで書かれた処理を実現させています。これは非同期処理の実行順序や制御を理解する上では避けて通ることのできない非常に重要な概念であり、根本的に非同期処理を実現させるために必要な機構です。

イベントループの機構によってコールバック関数といった単位で処理を切り替えることで「並行 (concurrent) 処理」が実現されます。それらのコールバック関数は並行 (concurrent) で処理される訳ですから、ソースコードの配置とずれて非同期的に実行されることになります。そしてコールバック関数はタスクかマイクロタスクとして処理される訳ですが、それぞれで実行されるタイミングや考え方が異なります。

イベントループはタスクやマイクロタスクについてそれぞれ専用のキューを持っています (タスクキューは複数ありえますが、マイクロタスクキューは通常1つです) が、そのキューに配置されたタスクとマイクロタスクは特定のタイミングでコールスタックと呼ばれるところへ順番に配置されて、コールスタックのトップとなったときに処理されます。そして、タスクやマイクロタスクの実体は実行コンテキストでした。

そしてイベントループにおいて重要なのは タスクやマイクロタスクの連鎖的な処理 です。非同期処理ではどのように処理順番(オーダー)を担保していくか、つまりどのように逐次処理を実現していくかが重要になりますが、タスクやマイクロタスクが連鎖的に発行されることで時間的に非連続になる可能性のある逐次処理を実現していきます。そして、その順番をうまく設定するための制御方法を学ぶことが非同期処理の学習という話でした。

参照チャプター

非同期 API による並列的作業

実際には「非同期」という概念は「複数の関連する事象が前の事象の完了を待たずに起きる」ことを意味し、非同期 API そのものの性質を指し示していました。そして、この「非同期」の現象は他の「同期」処理コードなどが存在しているときに限って発現し、イベントループの機構で並行 (concurrent) 処理されるコールバックなどは、この非同期 API を利用することに付随してタイミングがずれてしまうものでした。

非同期 API は「Non-blocking API」とも呼ばれ メインスレッドをブロッキングすることなく時間のかかる処理を環境がバックグラウンドで並列的に代行してくれました。API そのものは ECMAScript の一部ではなく、環境から提供される機能だったわけです。

JavaScript はメインスレッドという単一スレッド (シングルスレッド) で実行されるという話でもありましたが、「非同期処理」の目的はこの時間のかかる処理を環境にバックグラウンドで並列的に行わせている間も メインスレッドで別の作業ができるようにすること でした。そして、ここでの「並列的」という言葉は厳密な「並列 (parallel) 処理」ではなく JavaScript エンジンの外側で同時に複数のことが起きていることを意味します。

本質的には「時間効率の良い」非同期 API を利用したいからわざわざ「非同期処理」という制御の難しいことをやるわけです。ブラウザ環境ならレンダリング処理もこのメインスレッドで行われている訳ですから、長時間かかるような API 処理でスレッドをブロッキングしてしまうとユーザーがクリック操作などなにもできない時間ができるので更にシビアな問題になります。

そして、非同期 API は直接タスクを発行する古いタイプのものや、Promise インスタンスを返し間接的にマイクロタスクを発行する新しいタイプのものなどがありました。後者は Promise-based API と呼ばれ、モダンな非同期処理の根幹となるものです。

参照チャプター

すべては効率の良い非同期 API を使いたいがため

非同期 API は大きく2つのタイプがありました。setTimeout() といったタスクを発行する古いタスクベースの API と、queueMicrotask()fetch() といったマイクロタスクの仕組みに立脚したマイクロタスクベースの API です。これらの非同期 API を利用したいがために、Callback や Promise、async/await などを書いていくことになります。

結局のところ、そういった非同期にまつわる ECMAScript のシンタックスは環境がバックグラウンドで作業している間もメインスレッドで他の処理を実行できる「効率の良い」非同期 API の絡む処理がしたいから書かざる負えない「ただの手段」であり、制御が難しいので非同期 API を使わなくて良い場合にはわざわざ書く必要がありません。

非同期処理というテーマの目的そのものが「効率の良い非同期 APIを使う」ということにありますが、その際に発生するコード配置と実行順序がずれてしまう「後続の関連処理」をいかに記述するかという点が主な学習対象となる領域です。そして、非同期 API の処理を起点とした一連の処理手順 (逐次処理) の順序を保証することこそが非同期処理の制御では重要です。

参照チャプター

非同期 API の後続処理を制御するためのシンタックス

非同期 API を使う際にはタイミングがズレてしまうコードを記述するための上記のシンタックス (Promise や async/await) を利用しなくてはいけません。具体的には、非同期 API の処理を行ったあとにその API 処理から取得したデータなどを使った関連する後続処理を制御するために非同期のシンタックスを書くことになります。あるいは Promise-based な非同期 API を内部的に使用した抽象化された関数やメソッドなどを利用する際にも上層のレイヤで非同期のシンタックスを使用する羽目になります。

実際には、async/await は Promise のシステムに基づき Promise そのものを扱う Promise 処理の利便性を向上させるものでした。特に async 関数では Promise を扱っていることを意識する必要があります。つまり、async/await が主役なのではなく、あくまで Promise が主役というわけです。

非同期 API を起点とした一連の処理手順を正しく保証するためには、適切な Promise chain の構築や await 式の配置を行う必要があります。それらを適切に行うことで、意図した通りの処理の順序付けや並列化による効率化を実現して処理を制御できます。async 関数などの並列化を行う時でも、実際には 非同期 API 処理を並列化している ことに注意してください。

参照チャプター

TypeScript は型を上乗せするだけ

TypeScirpt は JavaScript に型システムを導入した言語ですが、本質的にはより良い JavaScript を書くための道具 (リンター) に過ぎません。非同期処理を理解するためにはイベントループの機構や JavaScript の実行環境とそこから提供される非同期 API、ECMAScript のシンタックスへの理解が欠かせません。

TypeScript によって上乗せされる型情報は本質的には非同期処理と関係がないものですが、モダンな開発では扱うデータの具象性を上げて堅牢でスケーラブルなコードを書くために学ぶ必要があります。

参照チャプター

まとめ

以上が「非同期処理」というテーマの話となりました。かなり長く壮大なテーマでしたが、裏側の原理や本質的な部分を押さえることで非同期処理の実行順序の制御のみならず、JavaScript や環境への理解を深めることができたと思います。

また、ECMAScript や JavaScsript、TypeScript などの境界線を意識することによって、今後わからないことがあればどの領域の話なのか追求できるようになったと思います。自分自身、非同期処理というテーマを通して様々なことを学ぶことができたと思っていますが、読んでくださった方も同様にこのテーマを通して多くのものを学ぶことができたと感じていただければ大変嬉しく思います。

ここまでお疲れ様でした。また、長い文章を読んで頂き誠にありがとう御座いました。最後のページはあとがきとなります。