非同期処理をjQueryが簡単に見せていた仕組み

jQueryは非同期処理を「簡単にした」のではなく「難しさを隠した」

結論から書きます。
jQueryは非同期処理を単純化したわけではありません。
非同期処理の複雑さを、開発者の視界から見えなくしただけです。

つまり、

  • JavaScriptが簡単になった

ではなく

  • JavaScriptの難しい部分をjQueryが肩代わりした

これが実態です。

そのため、jQueryを使っていた人が素のJavaScript(Promise / async/await)へ移行すると急に難しく感じます。
難しくなったのではなく、今まで見えていなかったものが見えるようになっただけです。

非同期処理が難しい本当の理由

プログラムの基本は「上から順に実行」です。

console.log("A");
console.log("B");
console.log("C");

これは必ず A → B → C の順で出ます。
これが同期処理です。

しかし通信は待ち時間が発生します。

fetch("/api");
console.log("A");

通信が終わる前にAが表示されます。
これが非同期です。

つまり非同期の本質は、
実行順序がプログラムの順番と一致しない
ことです。

ここから問題が始まります。

XMLHttpRequest時代の地獄

昔のコードを見てみます。

var xhr = new XMLHttpRequest();
xhr.open("GET","/user",true);

xhr.onreadystatechange = function(){
  if(xhr.readyState===4 && xhr.status===200){
    var user = JSON.parse(xhr.responseText);
    console.log(user.name);
  }
};

xhr.send();

console.log("終了");

出力はこうなります。

終了
(数秒後)ユーザー名

プログラムの下に書いた処理が先に動きます。
初心者が最初に混乱するポイントです。

さらに、複数通信になると一気に破綻します。

getUser();
getPosts();
getComments();

どの順で返るか保証がありません。
UIが壊れる原因になります。

jQueryが行った3つの隠蔽

jQueryはこの問題を3段階で隠しました。

1. コールバックの統一**

$.ajax({
  success: function(data){
    console.log(data);
  }
});

開発者は「ここに書けば後で実行される」とだけ覚えればよくなりました。
イベントや状態管理を意識する必要がなくなりました。

2. 状態の分離**

jQueryは通信状態を3つに整理しました。

  • success(成功)
  • error(失敗)
  • complete(終了)

XMLHttpRequestではreadyStateとstatusの組み合わせを自分で判定していました。
それをjQueryが分類しました。

3. Deferredによるチェーン化**

$.ajax("/user")
  .done(function(u){
    return $.ajax("/posts?id="+u.id);
  })
  .done(function(p){
    console.log(p);
  });

これにより「順番に処理する」書き方が復活しました。
非同期なのに同期のように書ける、これが最大のポイントです。

なぜ簡単に感じたのか

人間が理解しやすいプログラムは「流れ」があるものです。

しかし非同期は流れを壊します。
jQueryは「流れがあるように見せる」ことで理解を助けました。

内部ではこうなっています。

  • イベントキューに登録
  • 通信完了でコールバック実行
  • Deferredが次の処理を起動

つまり裏では非同期のままです。
表面だけ同期風になっています。

実際に起きていたこと(イベントループ)

ブラウザはイベントループで動いています。

1. JavaScriptを実行
2. キューを確認
3. コールバックを実行
4. 繰り返し

jQueryは、通信完了時にコールバックをキューへ入れます。
開発者はそれを「順番に動いた」と認識します。

つまりjQueryは非同期を消したのではありません。
イベントループを意識しなくてよくしたのです。

注意点:理解しないまま使うと必ず壊れる

典型的な間違いです。

var user;
$.ajax("/user").done(function(u){
  user = u;
});

console.log(user);

これはundefinedになります。
通信前にconsole.logが実行されるからです。

jQueryを長く使っていた人ほど、このバグに遭遇します。
「非同期を忘れて書ける」ことの副作用です。

現在のasync/awaitとの関係

現在は次のように書けます。

const res = await fetch("/user");
const user = await res.json();
console.log(user);

これはjQueryと似ています。
しかし重要な違いがあります。

async/awaitは非同期を隠していません。
「待っている」ことを明示しています。

jQueryは隠し、
async/awaitは表現しています。

まとめ

jQueryが普及した理由は、非同期処理を簡単にしたからではありません。
非同期処理を「理解しなくても使える形」にしたからです。

それは多くのWebアプリを生みましたが、同時に非同期の本質を見えなくしました。

現在のJavaScriptが難しく感じるとき、
それは仕様が複雑になったのではありません。
本来の姿に近づいたとも言えます。

jQueryは問題を消したのではなく、
問題を感じさせない優れたインターフェースだった、ということです。