1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptの非同期処理の変遷 ① コールバック関数の登場とその課題

Last updated at Posted at 2023-10-22

▼参考文献

Software Design 2023年9月号の中で非同期処理について詳しく解説されていて大変参考になったので、本記事を通じて学んだことを整理しています。

非同期処理はなぜ重要?

現代のWebページにおいて、処理を効率的に行い、良いユーザー体験を作るには非同期処理が重要。

特にWebAPIからのデータ取得といった時間のかかるタスクを同期処理で行うと、ユーザー体験に悪影響を与える。

非同期処理が重要になった背景には、Webページの進化が関係しています。

90年代のWebページ

当時のWebページは静的で、ブラウザの仕事はHTMLを表示するだけで、ユーザーとの相互作用はサーバサイドが担っていた。

JavaScriptの登場

1995年のJavaScriptの登場により大きく変化。JavaScriptはブラウザ向けに開発された言語で、ユーザーがWebページと動的に相互作用できる強力な特徴を持っている。

クライアントサイドアプリケーションの複雑化

JavaScriptを用いたWebページは、入力のバリデーションを行うような単純なものから始まり、GmailやGoogle Mapsに代表される複雑なWebアプリケーションというジャンルに反転した。
最近はWebAPIからのデータ取得、ユーザー入力の処理、画面描画などありとあらゆる処理をクライアントサイドで行うことを前提としたアプリケーションも一般的になっている。

ここで効率的なタスク処理が重要になってくる。特にWebAPIからのデータ取得といった時間のかかるタスクを同期処理で行ってしまうと、ユーザーインターフェースのタスクが停止することもあり、ユーザー体験に悪影響を与える。

そこで非同期処理が重要になってくる。

そもそも同期処理と非同期処理の違いは?

同期処理と非同期処理は、タスクがどのように実行されるかを指す言葉

同期処理 非同期処理
タスクを直列に実行 タスクを並行に実行
タスクを一つ一つ順番に実行する 複数のタスクを同時に実行する

同期処理の問題点

待ち時間(ブロッキング)を有効活用できないこと。待ちの間は、コンピュータの計算資源が有効活用できないため、パフォーマンスの問題に繋がる。待ちの間に実行できない処理の中にはユーザーと相互作用するUIの処理も含まれるためユーザー体験の悪化にも繋がる。

非同期処理が解決してくれること

非同期処理はノンブロッキングであるため、待ち時間に計算資源を有効活用でき、UIの処理も並行して実行できるため、ユーザー体験を損なわずに済む。

非同期処理を実現するために用いられたコールバック関数

コールバック関数とは、他の関数に引数として渡される関数で、特定のイベントで実行される。特定のイベントで実行したいタスクを関数に書いておき、その関数をコールバック関数として渡すことで、非同期処理を実現できる。

登場人物が多いので整理、、、
コールバック関数:他の関数に引数として渡される関数。特定のイベントの際に実行したいタスクを書く。
コールバック関数が渡される関数:何らかのタイミングで非同期的にタスクを実行するためのトリガー。

これにより、コールバック関数が設定されれば、、、

  • コールバック関数の完了を待つことなく他の処理を進めることができ、
  • 特定のタイミングにコールバック関数が実行されるという流れになる。

以上の流れにより、待ち時間を有効活用することで全体的なプログラムのパフォーマンスを向上させることができる。

コールバック関数の問題

コールバック地獄 callback hell

コールバック関数により、非同期処理が実現できるようになったが、新たな問題も発生。
多くの非同期処理を連続して行う場合、コールバック関数が入れ子状になり、可読性が低下する。この問題をコールバック地獄という。

以下がコールバック関数の具体例です。

処理内容:特定の投稿、その投稿者、その投稿者が属する組織を順に取得する非同期処理

  1. まずgetPost関数が非同期で投稿を取得する
  2. その投稿はpostとしてコールバック関数にわたる
  3. そのコールバック関数内で、postからuserIdを取り出しgetUser関数に渡す
  4. getUser関数も非同期的に投稿者を取得し、それが次のコールバック関数に渡る
  5. 同様にgetOrganization関数も非同期に組織を取得する
getPost(postId, (post) => {
    getUser(post.userId, (user) => {
        getOrganization(user.orgId, (org) => {
            // さらにコードがネストされて読みにくくなる
        });
    });
});

上記のように、非同期関数の処理結果を連鎖して扱う必要が出てくるとネストが深くなり、コールバック関数が生じる。現実はさらにさまざまなロジックや条件分岐が入ってくるため可読性はますます悪くなる。

不統一なコールバックAPI

どのようなAPIであれ、一貫したパターンの方が開発者が各APIを個別に理解する必要がないため利便性が高くなる。

しかし当初コールバック関数APIには標準的なパターンがなく、各APIは独自のパターンを持っていた.

コールバックAPIが不統一なことから、
「開発者は各APIごとにコールバックパターンを正しく理解する」必要があり、
実装の障壁となったり、コールバック地獄をさらに複雑化させたりする要因になっていた。

コールバック地獄や不統一なコールバックAPIなどの問題を解消する方法の1つとして、Promieやasync/awaitが登場する。
これらを利用することで、非同期処理のコードがより読みやすく理解しやすくなっていく。


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?