この記事を書いたきっかけ
弊社デザイナの @tsudaryo1715 からリクエストを受け、同期・非同期のことで理解しがたいことがある様子なので、この記事で伝えていきたいと思います。
@tsudaryo1715 は現在、デザイナからフロントエンドエンジニアへの転身に向けてWebの基礎的な技術を積極的に学んでいる最中で、その一端としてAjaxについて勉強しています。
なので、ごまかしてわかった気にさせるのではなく、正しく理解できるように伝えられればいいなと考えています。
この記事が @tsudaryo1715 を含む、Web技術を学び始めた人々の理解の助けになれば幸いです。
投げられた質問
何も知らないところからAjaxを学んだのでまとめてみたという記事の中で2つの質問を受けています。
- 一部の情報をwebブラウザからサーバーへリクエストするのは同期通信も一緒。なぜ真っ白にならないの?ロジックが知りたい。
- じゃあ全部非同期にすればええやん
それでは、一問一答ではなく、問題を解きほぐしながら説明していきましょう。
非同期通信すれば真っ白にならないということ
結論
長々と説明を書いているので先に結論を示します。
Ajaxが画面を真っ白にせずに描画を更新するのは、非同期通信の恩恵ではなくブラウザの挙動に対して非同期だから。
また、質問の背景にあると思われる
webブラウザからサーバーへリクエスト
はJSもネットワークに対してWebブラウザを操りアクセスしているという表現であり、正しい表現だと言えます。
けれども、混同を避けるためには、Webブラウザの素の挙動である「ブラウザアクセス」と「JSによるネットワークアクセス」を区別しておくと、正確性はちょっとかけるかもしれませんが、わかり易い表現になるかもしれません。
そもそも非同期とはなにか
多分、この「非同期(Asynchronous)」という言葉が混乱を生んでいると思います。
何に対して同期的なのかというのは主体があって初めて成り立つ言葉ということを意識してみましょう。
擬人化するとこんなところ1。
これは«抱擁(あるいはキス)»というイベントに付随した同期・非同期処理の例です。
左の人物
イベントに対して同期的な思考(処理)が行われ、このイベントが完了するまでは次のアクションが行われません。
右の人物
- キングダムの発売日
- 発売日を思い出したことで、このあと本屋によることを検討していることでしょう
- バグの原因特定
- イベントによって何かが想起され、バグを特定できたようです。素晴らしい! 解消方法も気づいているはずです。
- 幸せ
- 総合して幸せなようです。抱擁に幸せを感じていることと信じています。
非同期的なこと
本の購入やバグフィックスは抱擁というイベントから独立して進行していき、抱擁が終わったあとも処理が完了するまで継続します。
これこそが非同期的な処理です。
同期的なこと
非同期処理の一方で、幸せという感情はおそらくイベントの終了とともに減退するはずです。
また、それぞれの思考は関連しあっているわけではなく、ひとまとまりの思考として存在しているため、同期的なものとみなすことができます。
つまり、本の発売日を思い出し本屋へよるという行動の一貫性は同期的であり、バグの解消方法にたどり着き翌日にバグフィックスすることもまた、行動の一貫性という点で同期的であるとも言えるのです。
同期と非同期は視点と対象による
さて、これらの話を踏まえてこの図をもう一度見直してみます。
左の人物はイベントに対して終始一つの処理を行っており、イベントの開始・終了に対して同期的です。
右の人物はイベントを契機に複数の処理が発生し、そのうちのいくつかはイベントが終わっても続く非同期処理です。
しかし、処理そのものは「バグフィックスする」「本を購入する」というそれぞれの完了条件に対して同期的な処理とみなすこともできます。
さらにいえば、右の人物にとっては身体という制限の中で行動しなければならないので、「抱擁する」「バグフィックスする」「本を購入する」という行動は同期的に、各行動の終了を待ち合わせなければならないことでもあります。
混乱しそうなので視点ごとにまとめてみましょう。
抱擁イベント視点
対象の思考・行動 | 同期・非同期 | 理由 |
---|---|---|
左の人物の思考 | 同期 | 左の人物がイベントを終了させる意図を持たなければ終了しない |
本の購入 | 非同期 | 本を買うのはさすがに抱擁が終わったあとだろう |
バグフィックス | 非同期 | 同上。このままバグフィックスできるならそれはそれで幸せかもしれない |
右の人物の幸福感 | 同期 | 幸せの起因となっている(はずの)イベント終了によって終わるため |
左の人物視点
対象の思考・行動 | 同期・非同期 | 理由 |
---|---|---|
左の人物の思考 | 同期 | この思考が終了しなければ次のことが考えられない |
抱擁イベント | 同期 | いつかは強い意志でその手を離さなければならない |
右の人物の思考についてはアクセスできないので同期も非同期もない |
右の人物視点
対象の思考・行動 | 同期・非同期 | 理由 |
---|---|---|
発売日の想起・脳内デバッグ | 非同期 | 忘れずに未来の自分にたくそう |
抱擁・本の購入・バグフィックス | 同期 | それぞれ順序立てて終わらせなければならない |
本の購入イベント視点
対象の思考・行動 | 同期・非同期 | 理由 |
---|---|---|
右の人物の購買欲 | 同期 | 購入するまで購買欲は晴れない |
右の人物のデバッグまでの安心感 | 非同期 | キングダム読んで虫取り忘れたら困る |
デバッグイベント視点
対象の思考・行動 | 同期・非同期 | 理由 |
---|---|---|
右の人物の購買欲 | 非同期 | バグ取れても趣味は大事やで |
右の人物のデバッグまでの安心感 | 同期 | まずは再現させよう |
非同期なプログラム
コンピュータ・Webの世界に話を戻すと、実行主体から見て各処理が同期的であるかどうかを指して、我々は同期処理・非同期処理と呼んでいるわけです。
なので、今目の前に書いているソースコードの中で呼び出している処理が終わるのを待つのか、処理が終わらずとも次の処理へ進むのか、その処理の性質を同期・非同期と読んでいます。
呼び出し先の同期・非同期は本来的には無関係です2。
Ajaxの非同期とはなにか
ようやくAjaxの話に戻ってきました。
Ajaxはどうして非同期と銘打っているのかを理解するためには、ブラウザの仕組みの理解とWebの歴史を振り返る必要があります。
ブラウザの仕組み
ブラウザを構成している要素を単純化すると、こんなものだと思っていただければいいと思います。
構成要素 | 役割 |
---|---|
UI | ユーザが目にする部分 |
レンダリングエンジン | HTML/CSSからUIを構成する |
JSエンジン | レンダリングエンジンやその他のブラウザ機能を動的に操作するための言語を動かすランタイム |
そして、UI操作を行ってページを読み込むと、必ずすべてのUIに関わるものをリセットします。
なぜなら、直前のコンテキストによって表示が崩れてしまうことなどが発生すると困ったことが起こるからです。
Webの歴史:Ajax以前
基本的にすべてのWebは静的なHTMLをブラウザに渡し、それを表示するものでした。
Webブラウザが主に利用するものがHTML(HyperText Markup Language)というように、出自はハイパーテキストをオンラインでつないだものに過ぎません。
つまり異なるコンピュータ間を移動できるだけの文書の集まりとリンク集に過ぎないわけです。
そして、文書なので当然コンテキストは不要です。
どこから飛んできたとか、そういったことを一切考えず、表示すべきものをブラウザが取得し、それを正しくレンダリングすることが大事です。
これがWebブラウザはサイトの取得・表示についてステートレスであるということです。
その中でSSI/CGI/DHTMLなどの形で「ちょっと凝ったもの」を作る人達が生まれました。
ステートレスのWebに状態を持たせたのです。
<現在時刻><今までの訪問者数><以前に投稿してきたメッセージ>といった状態をもとに、リクエストに対してHTMLを変化させて返却するようにしたのです。
クライアント
-サーバ
の関係で見てみると、サーバ側で状態を保持・取得することによってHTMLを変更する方式ですね。
これは、Webブラウザにとってそれまでのステートレスな世界と変わりありません。
あくまでユーザのUI操作に従って指定のURLの情報を取得しレンダリングするだけです。
なので、リクエストするときには必ずUIをクリアして、真っ白な状態から得られた情報をレンダリングするわけです。
Webの歴史:Ajax登場
こんなWebでも情報は蓄積され、アクセスでき、大変に有用でした。
しかし、それだけでは満足できない人々もいます。
コンピュータ上の他のアプリケーションに比べて、UIは貧弱で、インタラクティブ性に劣り、ページ読み込みのたびに待たされることが我慢ならないのです。
そう、プログラマは短気で怠惰で、そして傲慢なのです。
なので不満を解消するための方法を見つけます。それがAjaxです。
Webブラウザがページのリクエストからレンダリングまでを同期的に行うから、ユーザやプログラマの欲望を満たせないのであれば、Webブラウザの一連の処理とは非同期的に処理をしてしまえば、読み込み直すこともなくなるのです。
やり方はすでにいろいろなところで解説してありますが、アイデアとしては以下のような感じです。
- ブラウザの挙動に頼らない(リンクを押したらリクエストして描画するとか)
- クリックとかキー入力の挙動をJSで制御する
- 必要なデータはJSで取得しに行く
- 取得したデータをもとに、クライアント側の現在の状態を利用して今表示されている画面を変更する
AjaxのAsynchronous(非同期)とは非同期通信のこと(だけ)ではない
結果的に非同期通信を利用することになるけれども、非同期通信だからブラウザを真っ白にしないのではないのです。
例えば、ajaxを通信と同期的に使いながら地図を作ることも可能です。その場合の挙動としてはこんな感じになるでしょう3。
- 表示されている地図の東側をクリック
- 該当の地図画像を取得
- この間、ユーザ操作は受け付けない
- 画面上はフリーズしたように見えるが真っ白にはならない
- 画像を取得したら表示されている地図画像を置き換える
これも立派なajaxの利用方法です。
例えば決済画面をajaxで作るのであれば同期的に処理をするでしょう4。
決済をリクエストしてからアイテムを入れ替えたりすることを防ぐために、画面を白くせずに処理を止めておいて決済完了したらそのようにレンダリングします。
ブラウザに対して非同期なAjax
つまり、先述のように主体と対象という目で見ると、「ボタンを押したイベント」という主体から見た「ブラウザ」の挙動として、Ajaxを用いない場合は画面の初期化〜表示までを同期的に行わなければならなかったことに対して、画面の表示や操作性を維持したままブラウザの挙動を止めずにリモートのデータを用いて非同期で処理をすすめることをできるようにしたのがAjaxなのです。
また、ブラウザを主体にJavascriptを観察すると、Javascriptの非同期性も見えてきます。
ブラウザはリクエストを受け付けてレンダリングするまでで、基本的な処理は終了しているのです。しかし、レンダリングされた情報をもとにインタラクティブな動きを実現するために作られたJavascriptは、ブラウザにとってはいつ発生するかわからないイベントでいつ終わるわからない処理を実行するために、ブラウザの挙動からは非同期的に動くようにしていたのです。
そしてJavascriptを主体として通信を見たときに、通信が終わらなくても次の処理(画面の操作が可能な状態へ戻す)ことは非同期通信ですが、画面が白くなることを防ぐこととは別の観点での話になります。
ユーザビリティを理由として大体が非同期通信を選択することになるのは結果であって、画面表示とは直接関係ありません5。
すべて非同期にすればええやん
結論
そのアイデアがSPA(Single Page Application)です。
色々SPAについては情報があるので割愛してよさそうですが、SPAの話も少しだけしていきましょう。
SPAのアイデア
@tsudaryo1715 のいうように、そんなに非同期通信で画面更新することにメリットがあるのなら全部非同期にすればいいというアイデアで間違いありません。
ただ、それだけでブラウザ全体を非同期にする事はできません。
今までWebはステートレスである前提で成り立っています。
突然、前に見ていた情報が残り続ける前提で作り直すわけにはいかないでしょう。
現存するすべてのWebサイトが、リクエストされてきたら最初に画面を初期化することをブラウザに指示する必要が出てくるのです。
そこで、自分のサイト内だけでも非同期通信だけで作り上げることがSPAです。
SPAのメリット
- 読み込み時間が短縮できる
- シームレスな可能
- つまりユーザ体験が向上する
SPAの難しいところ
- 前の情報が残るので、どのような経路でユーザが「この」ページを見ているのかをトラッキングしなければ整合性が崩れる
- URLが変わらないとすべてが「最初のページ」から辿らなければならない
- 検索エンジンから認識されるページが1ページだけになってしまう
解決策
- 現在のページで「あるべき姿」を記述すれば、どのような経路をたどっていても正しくなるような基盤を整える
- URLを変えられるように「見た目上のURL」と画面をブラウザに認識させる
これらのことを(他にもいろいろ)やってくれるのが最近のフロントエンドフレームワークなんです。
そして、経路を気にせずに正しくレンダリングするために仮想DOMを作ったりしています。
必ずしもSPAのために仮想DOMが必要なのではなくて、うまくhtmlを作れる/DOMを変更できるのであればそれ以外の実装もできるわけです。
頑張ればjQueryでもSPAは書けるんです。現実的ではないですが。
仮想DOMを使わずに作られたF/WがSvelteだったりして、最近はまたフロントエンドの戦国時代が始まるかもしれませんね。
マサカリ歓迎
ブラウザを作っているわけでもなく、F/Eを専門としているわけでもないB/Eエンジニアによる説明のため、誤りが含まれている可能性があります。
また、いくつか個人的な解釈を加えている部分もあるかと思います。
記事の質向上のためにも皆様の忌憚のないご意見をお待ちしております。
-
筆者が男性なので男性と思しきアイコンが滑稽になるような図ですが、性差別への意図は一切含まれておりません。また、フリー素材を反転させておりますが、説明順序的に左から同期・非同期とつなげていきたかったためであり、他意はございません。 ↩
-
JSでは
await
async
キーワードとともに使うことが多いため、呼び出し先の同期・非同期(Promise
を返すか)を知る必要が生まれます。 ↩ -
ただでさえセンシティブな決済画面をajaxで作ることは、F/E苦手な僕からするとちょっと避けたいですが ↩
-
ajaxが生まれた頃のブラウザは、jsが処理をブロックするとレンダリングを放棄して画面が白くなることがありました。そういう意味では非同期通信であることにも意味はあったのです。昨今のブラウザではjsのブロッキングとすでにレンダリングされている画面の因果関係は薄れているはずです。 ↩