Chapter 01

はじめに

PADAone🐕
PADAone🐕
2023.02.07に更新

このチャプターについて

このチャプターでは、この本を読むに当たっての注意点や本の内容についての説明を記載しています。具体的な解説を読み始める前に必ず確認するようにしてください。

解説の間違いについての注意

この本について

この本を書き始める以前に、記事として『非同期処理を理解できるようになるまでので学習ロードマップ』を執筆しました。その結果、大きな反響があったため「学習のロードマップは分かったけど、より具体的な非同期処理のポイントも知りたい」といった一歩進んだ要望に応えようとこの本を書きました。

執筆当初はプロミスチェーン(Promise chain)とイベントループ(Event loop)という2つのポイントを軸に非同期処理を解説する本として制作していましたが、現在では更に拡張して以下のように非同期処理の全体的なメカニズムを理解するために横断的なストーリーを解説するような本となっています。もちろんそれぞれの要素についても深堀りすれば書きつくすことができないので、ある一定のレベルまで解説してそれらの要素を繋ぎ合わせるような解説となっています。

必要な知識のライン

ただし、学習経験から重要な情報に沿ってロードマップ的な内容を再構成した作りとなっているため、内容重視の解説順番となります。上図にあるように非同期処理を理解するために「必要であることが明らかではない知識」というものが実は存在してます。通常これらの知識は非同期処理の学習後半に突入してから「どうやら必要らしい」ということが判明するものであり、これに気づかずに飛ばしてしまうと永遠に非同期処理の謎が解けないという罠があるので、まずはここから解説し始めるような本となっています(図にあるように制御や ECMAScript 仕様などを学習する前にこれらを駆逐しておく必要があります)。

執筆目標としては「読者が非同期処理の制御の流れを理解し、予測できるようになる」ことを掲げています。また、学習の過程で陥りがちなトラップや誤解などについても実際の学習から得た知見で解説しているので、「非同期処理について学習してみたけど、やっぱり分からない」という方の誤解や勘違いを解きつつ「非同期処理の謎を解明する」ということも目標としています。

今回あえて Book(本) として公開している理由の一つとしては、かなり長い内容になるので記事一本では書ききれないと感じたためです。実際、アウトプット用の記事として書いたものが予想以上に長くなってしまったため、本へと再構成しました。

本としての体裁は取っていますが、筆者自身のアウトプットを大きく兼ねているため、この本は無料公開となっています。

この本では可能な限り正確な情報になるように努力していますが、学習者のアウトプットとしての側面も強いので、間違いや勘違い等があるかもしれません。その点についてはご容赦ください。すなわち、この本は既存の解説に対しての疑問点や矛盾点を解消したり、多数の断片的な情報をピースとして一貫性のあるストーリーを構築するなどの自分自身の学習プロセス自体を組み込んだボトムアップの解説書となります。

そういった前提から、なにか不備や間違いを見つけた場合にはスクラップや Github の issue などにて教えていただけると非常に助かります。

対象読者について

想定している対象読者は「非同期処理の理解に悪戦苦闘している人」です。

従って、本当にゼロから非同期処理を学習したいという人にはこの本は難しすぎるかもしれません。「非同期処理について一度学んでみたが分からない」という人や「どうしてこのような実行順序になるのか分からない」という人向けの内容となります。

とはいえ、非同期処理を学習する上でトラップとなる点や深堀りすべき点については徹底的に記載しているので、ゼロから学習したい場合には他のリソースを併用しながら読むことを推奨しています。

構成について

この本は大きく分けて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』が非常におすすめです。非同期処理の基礎やシンタックスについて網羅的に学ぶことができます。

https://jsprimer.net/basic/async/

もちろん、MDN のドキュメントも必須です。何か細かいことを知りたくなったら、MDN のドキュメントを読んでください。ただし、場合によっては英語版も読む必要があります[1]

https://developer.mozilla.org/ja/

動画では基本的に JSConf[2] にアップロードされているものがオススメです。実はドキュメントよりも動画を使って視覚的に理解した方がいい場合もよくあるので、ぜひ活用してください。JSConf の動画は本質情報を得られる場合が多いです。この本の中ではも JSConf で行われた『What the heck is the event loop anyway?』などのいくつかの動画を参照しています。

https://www.youtube.com/c/JSConfEU/videos?view=0&sort=p&shelf_id=0

この本で主に使う JavaScript の実行環境である Deno については uki00a さんの『Effective Deno』が分かりやすいので参考にすると良いと思います。

https://zenn.dev/uki00a/books/effective-deno

「そもそも Deno とか Node とかって何なの?」という疑問がある場合には non_cal さんの以下の記事などが分かりやすいので参考にしてください。

https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb

これら以外で細かい学習リソースに困ったら、『非同期処理を理解できるようになるまでので学習ロードマップ』の記事に挙げたリソースなどを参考にしてもらえばいいかと思います。

スクラップとリポジトリについて

この本に関する感想や質問等あれば、紐付けられたスクラップの方へお願いします。間違いや誤植等を見つけた場合にも教えていただけるとありがたいです。

https://zenn.dev/estra/scraps/20dc6c4a1b64f8

また先日、今まで使用していた Zenn のリポジトリを無料公開用のリポジトリとして Public にしました。以下のリポジトリでタイポや内容修正などのプルリクエストを受け付けています。

https://github.com/yo-goto/zenn-public-repo

Book の各チャプターの右下にも「GitHub で編集を提案」のボタンがあるので、ミスや間違い、タイポなどを見つけた場合にはプルリクエストを作成していただけると助かります。

GitHubで編集を提案

表記について

イベントループで Promise chain がどのように動くか "JavaScript Visualizer 9000 "というツールをこの本の各所で使用していきます。名前が長いので、以降「JS Visualizer」などと呼称するようにします。

また、関数の引数に渡すコールバック関数について then(cb)then(callback) のように cbcallback と省略表記する場合があるので注意してください。

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

https://deno.land/x/deno@v1.20.4

Deno では設定無しで TypeScript をすぐ実行できるという利点があります。そのためこの本で Deno を利用しているのは、最終的に TypeScript で Promise の型を考えるために使っているという側面もあります。Deno を使うことで JavaScript に型情報の操作を加えた TypeScript へとスムーズに移行していくことができます[4]。という訳で、この本の第4章最終チャプターでは JavaScript から TypeScript へ移行する方法と Promise の型注釈についても解説します。

サーバーを立てたり HTML ファイルなどを噛ませず、以下の様に JS ファイルを用意してターミナルから deno run コマンドを使ってローカル環境で JavaScript を実行していきます(node コマンドでもいいです)。これが JavaScript の非同期処理をテストするための最も早い方法です。

helloWorld.js
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

https://nodejs.org/dist/v18.2.0/docs/api/

そして、そういった特定の環境に拘らず考えるため、途中から V8 エンジンを使用します。この V8 エンジンについても解説しますが、V8 エンジンは Chrome, Node, Deno, Electron などで使用されている JavaScript エンジン(ECMAScript を実装しているもの)です。

この本で使用する V8 エンジンのバージョンは次のものとなります。

❯ v8
V8 version 10.3.154

JS Visualizer 使用上の注意点

JS Visuzalizer では使用する上でいくつか注意点があります。

https://www.jsv9000.app

まずは、日本語が使えませんので、日本語を入力したり、日本語が含まれるコードをペーストしようとすると画面が白くなり固まります。

このツールは非同期処理を理解する上で非常に有用であり欠かせませんが、使用して行くうちにいくつかの疑問点が湧いてくるはずです。筆者自身も非同期処理を理解していく内に、この違和感に気づきました。

実は JS Visualizer では以下のような実装ミスとも言える点や勘違いしやすい点があります。

  • 環境での並列的作業としての setTimeout() 関数などの Web API の概念が掴みづらく、タスクキューにタスクが挿入される順番に間違いと思われる部分があります
  • イベントループには多くの場合、タスクキューが1つ以上あるため、タスクキューが1つであると勘違いする可能性が高いです
  • コールスタックに積まれるコンテキストとして必要なグローバルコンテキストが視覚化されていないため、最初のタスクやマイクロタスクの実行順番について誤解する可能性があります
  • then() に登録したコールバック以外に追加発生するマイクロタスクが視覚化されないため、実行順序がなぜそうなるのか分からないというケースが存在します

そのため、この本でも途中から(新しい知見に基づいたチャプターでは)あまり頻繁に使用しなくなりますが、イベントループのイメージを掴む上で非常に有用であることは変わりませんので注意して利用するようにしてください。

ChangeLog

大きな変更のみトラッキングしています。

ChangeLog
脚注
  1. MDN のドキュメントは未翻訳の箇所に重要なことが書かれていたり、翻訳時点でのドキュメントよりも分かりやすい最新版などが確認できる場合がたまにあるため、両方確認した方がいいです。この本でも英語版ドキュメントからの引用がいくつかあります。 ↩︎

  2. Conf 系(NodeConf, ReactConf など)はオーソリティ性が確保されているのである程度安心してみることができます。基本的には最新のものが良いですが、JSConfEU の動画ではよく視聴されているものを見ておくと良いです。動画は英語ですが、平易な英語なので字幕などを活用して視聴してください。 ↩︎

  3. 最初はタスク(Task)よりもマクロタスク(Macrotask)の単語の方が理解しやすいかと考えていたのですが、タスク(Task)の方が本質を捉えやすい言葉だと感じたのでこちらをなるべく使っていきたいと思います。加えて Macro と Micro は似ているので見間違いやすいという理由もあります。 ↩︎

  4. Deno は V8 エンジンでの JavaScript をベースにしつつ、型の世界(TypeScript)へと簡単に立ち入ることができる環境であり、段階的に「型情報の操作」に関する学習を進めることができます。また、node で利用する ts-node などの追加パッケージが必要なく TypeScript ファイルをコマンドラインから実行できます。 ↩︎