このチャプターについて
このチャプターでは、この本を読むに当たっての注意点や本の内容についての説明を記載しています。具体的な解説を読み始める前に必ず確認するようにしてください。
解説の間違いについての注意
この本について
この本を書き始める以前に、記事として『非同期処理を理解できるようになるまでので学習ロードマップ』を執筆しました。その結果、大きな反響があったため「学習のロードマップは分かったけど、より具体的な非同期処理のポイントも知りたい」といった一歩進んだ要望に応えようとこの本を書きました。
執筆当初はプロミスチェーン(Promise chain)とイベントループ(Event loop)という2つのポイントを軸に非同期処理を解説する本として制作していましたが、現在では更に拡張して以下のように非同期処理の全体的なメカニズムを理解するために横断的なストーリーを解説するような本となっています。もちろんそれぞれの要素についても深堀りすれば書きつくすことができないので、ある一定のレベルまで解説してそれらの要素を繋ぎ合わせるような解説となっています。
ただし、学習経験から重要な情報に沿ってロードマップ的な内容を再構成した作りとなっているため、内容重視の解説順番となります。上図にあるように非同期処理を理解するために「必要であることが明らかではない知識」というものが実は存在してます。通常これらの知識は非同期処理の学習後半に突入してから「どうやら必要らしい」ということが判明するものであり、これに気づかずに飛ばしてしまうと永遠に非同期処理の謎が解けないという罠があるので、まずはここから解説し始めるような本となっています(図にあるように制御や ECMAScript 仕様などを学習する前にこれらを駆逐しておく必要があります)。
執筆目標としては「読者が非同期処理の制御の流れを理解し、予測できるようになる」ことを掲げています。また、学習の過程で陥りがちなトラップや誤解などについても実際の学習から得た知見で解説しているので、「非同期処理について学習してみたけど、やっぱり分からない」という方の誤解や勘違いを解きつつ「非同期処理の謎を解明する」ということも目標としています。
今回あえて Book(本) として公開している理由の一つとしては、かなり長い内容になるので記事一本では書ききれないと感じたためです。実際、アウトプット用の記事として書いたものが予想以上に長くなってしまったため、本へと再構成しました。
本としての体裁は取っていますが、筆者自身のアウトプットを大きく兼ねているため、この本は無料公開となっています。
この本では可能な限り正確な情報になるように努力していますが、学習者のアウトプットとしての側面も強いので、間違いや勘違い等があるかもしれません。その点についてはご容赦ください。すなわち、この本は既存の解説に対しての疑問点や矛盾点を解消したり、多数の断片的な情報をピースとして一貫性のあるストーリーを構築するなどの自分自身の学習プロセス自体を組み込んだボトムアップの解説書となります。
そういった前提から、なにか不備や間違いを見つけた場合にはスクラップや Github の issue などにて教えていただけると非常に助かります。
対象読者について
想定している対象読者は「非同期処理の理解に悪戦苦闘している人」です。
従って、本当にゼロから非同期処理を学習したいという人にはこの本は難しすぎるかもしれません。「非同期処理について一度学んでみたが分からない」という人や「どうしてこのような実行順序になるのか分からない」という人向けの内容となります。
とはいえ、非同期処理を学習する上でトラップとなる点や深堀りすべき点については徹底的に記載しているので、ゼロから学習したい場合には他のリソースを併用しながら読むことを推奨しています。
構成について
この本は大きく分けて5つの章から構成されています。上の「非同期処理のメカニズムを理解するために必要な知識」の図にある内容を学習において重要な順番に並べて解説しています。上図は必要な知識について俯瞰的に確認するためのものなのでそれぞれの章と完全一致していないことに注意して下さい。
- 第1章 - API を提供する環境と実行メカニズム
- 第2章 - Promise インスタンスと連鎖
- 第3章 - async 関数と await 式の挙動
- 第4章 - 制御と型注釈
- 第5章 - 仕様およびその他の番外編
第1章の内容はメタ的な視点での解説を含んでいるため、部分的に第2章や第3章の内容である Promise や async/await についての知識を利用している場合があります。難しい場合には、ざっと目を通して具体的な Promise の解説である第2章の内容を読み進めるのも良いかもしれません。
第1章から第3章までの具体的な流れとしては、非同期 API とそれを提供する環境についての話から始め、イベントループへの理解を深めます。具体的な非同期処理については、Promise の基礎的概念から Promise chain へと続き、そこまでのすべての知識と V8 エンジンからのアプローチによって async/await の挙動を理解します。また、これらの章内で非同期処理理解のために暗黙的に必要となる JavaScript のシンタックスに関する知識も解説しておいたので、必要になった段階で確認してください。
第4章では応用として Promise の静的メソッドと await 式の配置による複数処理や反復処理の制御方法を学び、最終的には TypeScript での型注釈を行えるようにします。第5章では番外編として Promise の仕様などのメインの解説で拾いきれなかったものを解説します。
第5章の後には非同期処理のテーマに関してのまとめと総括のチャプターを設けているので、そちらを見つつ他のチャプターの学習をすすめていくのもよいかもしれません。
内容としての充足度は 100% で、当初予定していた内容についてのチャプターはすべて収録済みとなり、最新の ECMAScript のシンタックスと TypeScript までを含めたモダンな解説になっています。また、筆者自身の知識が更新される限り内容を追加して最新に保つつもりです。※ 現時点では Dynamic import や Unhandled rejection などの解説は含まれませんが、将来的に入れる可能性があります。
参考文献について
各チャプターのいたるところで参考となる文献や参照したリソースの URL を貼っておきます。また、最終チャプターにて各チャプターのリソースの URL をまとめていますので気になった場合はそちらも参照してください。
もちろん、この本1つで非同期処理を完全に理解することは難しいので、それらを含めたインターネット上のあらゆる他のリソースを併用することを推奨しています(非同期処理の理解にはかなり多くの情報が必要なため、1つのリソースだけでは情報が不足しすぎています)。特に、この本で扱う領域は非同期処理の制御を予測するための基礎的な事象、あるいは本質的な事象を中心としています。より実践的な話については他のリソースが必要となります。
とは言え、非同期処理の解説等であまり語られないような環境や V8 エンジン、マイクロタスク、実行コンテキストなどの概念が登場するので読んで損がないようにしています。これによって非同期処理だけでなく、付随的・必然的に JavaScript の実行メカニズムの基礎についても理解できるようになります。
参考になるドキュメントでは azu さんの『JavaScript Primer』が非常におすすめです。非同期処理の基礎やシンタックスについて網羅的に学ぶことができます。
もちろん、MDN のドキュメントも必須です。何か細かいことを知りたくなったら、MDN のドキュメントを読んでください。ただし、場合によっては英語版も読む必要があります[1]。
動画では基本的に JSConf[2] にアップロードされているものがオススメです。実はドキュメントよりも動画を使って視覚的に理解した方がいい場合もよくあるので、ぜひ活用してください。JSConf の動画は本質情報を得られる場合が多いです。この本の中ではも JSConf で行われた『What the heck is the event loop anyway?』などのいくつかの動画を参照しています。
この本で主に使う JavaScript の実行環境である Deno については uki00a さんの『Effective Deno』が分かりやすいので参考にすると良いと思います。
「そもそも Deno とか Node とかって何なの?」という疑問がある場合には non_cal さんの以下の記事などが分かりやすいので参考にしてください。
これら以外で細かい学習リソースに困ったら、『非同期処理を理解できるようになるまでので学習ロードマップ』の記事に挙げたリソースなどを参考にしてもらえばいいかと思います。
スクラップとリポジトリについて
この本に関する感想や質問等あれば、紐付けられたスクラップの方へお願いします。間違いや誤植等を見つけた場合にも教えていただけるとありがたいです。
また先日、今まで使用していた Zenn のリポジトリを無料公開用のリポジトリとして Public にしました。以下のリポジトリでタイポや内容修正などのプルリクエストを受け付けています。
Book の各チャプターの右下にも「GitHub で編集を提案」のボタンがあるので、ミスや間違い、タイポなどを見つけた場合にはプルリクエストを作成していただけると助かります。
表記について
イベントループで Promise chain がどのように動くか "JavaScript Visualizer 9000 "というツールをこの本の各所で使用していきます。名前が長いので、以降「JS Visualizer」などと呼称するようにします。
また、関数の引数に渡すコールバック関数について then(cb)
や then(callback)
のように cb
や callback
と省略表記する場合があるので注意してください。
ECMAScript や HTML 仕様、Node.js などで同じもの(似たもの)を表す場合に用語が違ったりする場合がありますが、混乱を避けるために次の用語のみを意図的に使用するようにします。また、英単語を記載する際には大文字から始めるようにします。
-
イベントループ(Event loop)
-
タスク(Task)[3]
-
マイクロタスク(Microtask)
使用する環境とJSエンジン
この記事では実際のコードを通して、Promise chain がどのような制御で実行されるかを確認していきます。この本を読む際にはぜひ自分で実行してみるようにしてください。
JavaScript と一言で言っても様々な環境で使用される言語ですから、どういった環境で使用するかを考える必要があります。
- Chrome や Safari などのブラウザ環境
- Node や Deno といったランタイム環境
筆者は Deno がお気に入りなので、この本の説明で使用する JavaScript の実行は主に Deno 環境で行っていきます。
macOS であれば、Deno は Homebrew などでコマンドラインから簡単にインストールできます。他の OS でのインストール方法は公式マニュアルを参照してください。
# homebrew でインストール
❯ brew install deno
使用する Deno のバージョンは以下のものとなります。
❯ deno -V
deno 1.20.4
Deno では設定無しで TypeScript をすぐ実行できるという利点があります。そのためこの本で Deno を利用しているのは、最終的に TypeScript で Promise の型を考えるために使っているという側面もあります。Deno を使うことで JavaScript に型情報の操作を加えた TypeScript へとスムーズに移行していくことができます[4]。という訳で、この本の第4章最終チャプターでは JavaScript から TypeScript へ移行する方法と Promise の型注釈についても解説します。
サーバーを立てたり HTML ファイルなどを噛ませず、以下の様に JS ファイルを用意してターミナルから deno run
コマンドを使ってローカル環境で JavaScript を実行していきます(node
コマンドでもいいです)。これが JavaScript の非同期処理をテストするための最も早い方法です。
console.log("hello world!");
# deno run コマンドでスクリプトファイルを実行
❯ deno run helloWorld.js
hello world!
# node コマンドでスクリプトファイルを実行
❯ node helloWorld.js
hello world!
小さいスニペットをコマンドライン実行してテストするというこの方法を筆者は「コマンドライン JavaScript」と呼んでいます。ちなみに、この方法に利用できる環境は MDN では「JavaScript Shell」と呼ばれています。
A JavaScript shell allows you to quickly test snippets of JavaScript code without having to reload a web page. They are extremely useful for developing and debugging code.
(JavaScript technologies overview - JavaScript | MDN より引用)
後のチャプターで詳しく解説しますが、Chrome ブラウザ環境や Node, Deno といったランタイム環境においてイベントループの本質的な部分(抽象的な動作メカニズム)は共通しています。とはいえ環境による違いもあるので、それについても解説しておきました。つまりこの本では、代表的な複数の環境において JavaScript の非同期処理を包括的に考えます。
というわけで、Node 環境についても考える場面があるので node
コマンドを使用している際には次のバージョンで考えください。
❯ node -v
v18.2.0
そして、そういった特定の環境に拘らず考えるため、途中から V8 エンジンを使用します。この V8 エンジンについても解説しますが、V8 エンジンは Chrome, Node, Deno, Electron などで使用されている JavaScript エンジン(ECMAScript を実装しているもの)です。
この本で使用する V8 エンジンのバージョンは次のものとなります。
❯ v8
V8 version 10.3.154
JS Visualizer 使用上の注意点
JS Visuzalizer では使用する上でいくつか注意点があります。
まずは、日本語が使えませんので、日本語を入力したり、日本語が含まれるコードをペーストしようとすると画面が白くなり固まります。
このツールは非同期処理を理解する上で非常に有用であり欠かせませんが、使用して行くうちにいくつかの疑問点が湧いてくるはずです。筆者自身も非同期処理を理解していく内に、この違和感に気づきました。
実は JS Visualizer では以下のような実装ミスとも言える点や勘違いしやすい点があります。
- 環境での並列的作業としての
setTimeout()
関数などの Web API の概念が掴みづらく、タスクキューにタスクが挿入される順番に間違いと思われる部分があります - イベントループには多くの場合、タスクキューが1つ以上あるため、タスクキューが1つであると勘違いする可能性が高いです
- コールスタックに積まれるコンテキストとして必要なグローバルコンテキストが視覚化されていないため、最初のタスクやマイクロタスクの実行順番について誤解する可能性があります
-
then()
に登録したコールバック以外に追加発生するマイクロタスクが視覚化されないため、実行順序がなぜそうなるのか分からないというケースが存在します
そのため、この本でも途中から(新しい知見に基づいたチャプターでは)あまり頻繁に使用しなくなりますが、イベントループのイメージを掴む上で非常に有用であることは変わりませんので注意して利用するようにしてください。
ChangeLog
大きな変更のみトラッキングしています。
ChangeLog
- 2023-01-09
- 『第5章 - 仕様およびその他の番外編』のチャプターを追加
- 『Promise chain と async/await の仕様比較』のチャプターを追加
- 2022-12-19
- 『catch メソッドと finally メソッド』のチャプターで then/catch/finally についての情報を詳細に更新
- 2022-12-15
- 誤りのあった他の波及チャプターを修正し、修正チャプターを番外編と位置づけた
- 2022-12-14
- 『Promise.prototype.then の仕様挙動』の訂正・解説用チャプターを追加
- 2022-11-23
- 『第2章 - Promise インスタンスと連鎖』の章で間違いが見つかっため、その内容についての注意を記載
- 2022-11-05
- 『タスクキューとマイクロタスクキュー』のタスクキューについての解説に間違いがあったため修正
- 2022-08-15
- ブランチに纏わるあれこれの設定を変更し、リポジトリを Public として公開
- 2022-07-21
- 新チャプター『総括 - 非同期処理のまとめ』を追加
- 2022-07-18
- 新チャプター『イテレータとイテラブルとジェネレータ関数』を追加
- 2022-07-12
- 新チャプター『TypeScript における Promise の型注釈』を追加
- 2022-06-30
- 新チャプター『await 式の配置による制御』を追加
- 新チャプター『反復処理の制御』を追加
- 新チャプター『参考文献』を追加
- 2022-06-26
- 新チャプター『Promise の静的メソッドと並列化』を追加
- 2022-06-16
- 章分けのためのチャプターを追加
- 2022-06-15
- 新チャプター『同期 API とブロッキング』を追加
- 2022-06-06
- すべてのチャプターの説明を新しい知見に基づく内容へと書き換え完了
- 新チャプター『あとがき』を追加
- 2022-05-28
- 新チャプター『Top-level await』を追加
- 2022-05-22
- いくつかチャプターでの説明を加筆
- 2022-05-21
- 全チャプターのフォーマットを統一
- 2022-05-14
- 全体を完成
- 新チャプター追加
- 2022-05-06
- 新チャプター追加
- 『コールスタックと実行コンテキスト』を追加
- 『それぞれのイベントループ』を追加
- 『タスクキューとマイクロタスクキュー』を追加
- 『V8 エンジン』を追加
- 『非同期 API と環境』を追加
- 『resolve 関数と reject 関数の使い方』を追加
- 『古い非同期 API を Promise でラップする』を改修
- 『イベントループは内部にネストしたループがある』を改修
- 新チャプター追加
- 2022-05-03
- 『コールバックで副作用となる非同期処理』に「副作用とは」の項目を追加
- 新チャプター『Promise の基本概念』を追加
- 2022-04-23
- 『コールバックで副作用となる非同期処理』を大幅追記
- 2022-04-21
- JavaScript Visuzalizer で共有したコードが文字化けしていたので修正
- 『Event loop の概要と注意点』を大幅に修正追記
- それに伴い Event loop のステップについて各所を修正
- 新チャプター追加
- 『Promise コンストラクタと Executor 関数』で関数式とアロー関数の補足を追加
- 『コールバック関数の同期実行と非同期実行』で「コールバック関数はいつ実行される?」の項目を追加
- 『then メソッドは常に新しい Promise を返す』で「Promise の状態を確かめる」の項目を追加
- 『Promise chain で値を繋ぐ』で「チェーンの最後まで値を繋ぐ」の項目を追加
- 『Promise chain はネストさせない』で「fetch の例」の項目を追加
- 『Event loop は内部にネストしたループがある』を追加
- その他全体的に加筆修正
- コードの表記を修正
-
MDN のドキュメントは未翻訳の箇所に重要なことが書かれていたり、翻訳時点でのドキュメントよりも分かりやすい最新版などが確認できる場合がたまにあるため、両方確認した方がいいです。この本でも英語版ドキュメントからの引用がいくつかあります。 ↩︎
-
Conf 系(NodeConf, ReactConf など)はオーソリティ性が確保されているのである程度安心してみることができます。基本的には最新のものが良いですが、JSConfEU の動画ではよく視聴されているものを見ておくと良いです。動画は英語ですが、平易な英語なので字幕などを活用して視聴してください。 ↩︎
-
最初はタスク(Task)よりもマクロタスク(Macrotask)の単語の方が理解しやすいかと考えていたのですが、タスク(Task)の方が本質を捉えやすい言葉だと感じたのでこちらをなるべく使っていきたいと思います。加えて Macro と Micro は似ているので見間違いやすいという理由もあります。 ↩︎
-
Deno は V8 エンジンでの JavaScript をベースにしつつ、型の世界(TypeScript)へと簡単に立ち入ることができる環境であり、段階的に「型情報の操作」に関する学習を進めることができます。また、node で利用する ts-node などの追加パッケージが必要なく TypeScript ファイルをコマンドラインから実行できます。 ↩︎