jQuery 同期処理と非同期処理について

コードに書いてある順番に処理が進み、ひとつの処理をしている間は後続の処理は実行されない、これが同期処理です。サーバーから応答が返ってくるまでの間、別の処理が走り効率的に処理するのが非同期処理です。

解説

JavaScriptでは、Ajaxに限らず非同期処理を行うケースがあります。

<body>
    <div class="main">
      <div class="header">
        <h1> サンプル </h1>
      </div>
      <div class="content">
        <form>
          <button class="button" type="button" name="button">setTimeout</button>
        </form>
        <div class="desc"></div>
      </div>
      <div class="footer">
        <hr>
        <p class="copyright">2024 xxxx all rights reserved.</p>
      </div>
    </div>
    <script>
      $(document).ready(function(){
        $('button').on('click', function() {
          $('.desc').append('処理開始<br>');
            setTimeout(function() {
              $('.desc').append('タイマーが2秒経過をお知らせします<br>');
            }, 2000);
        $('.desc').append('処理完了<br>');
        });
      });
    </script>
    </body>

スクリプトの流れは、「処理開始」が表示され、「タイマーが2秒経過をお知らせします」と表示されてから、「処理完了」が表示されるように見えますが、実際は、処理開始と処理完了が即座に表示され、その後2秒後経過してから「タイマーが2秒経過をお知らせします」と表示されるのがわかります。

setTimeout()は非同期処理を行うので、タイムアウトを待っている間に、後続の処理が実行されます。

非同期処理とコールバック関数

非同期処理で「ある処理の応答があったら実行する」処理を記述するための方法が、コールバック関数です。コールバック関数としてsetTimeout()の引数にクロージャを指定していました。

setTimeout(function(){
 $('.desc').append('タイマーが2秒経過をお知らせします<br>');
});

コールバック関数を使うことで、非同期処理をわかりやすく記述できます。しかし、これも単純な処理にとどまっているからわかりやすいだけで、実際の開発では色々と問題がでてきます。例えばAjaxで複数のAPIを呼び出すケースは、応答に対するコールバックでさらに別のAPIへのAjaxの処理を行い、そのコールバックを処理します。しかもまたコールバックの中で非同期処理がとなると、階層は深くなります。

$.get('API1',function(){・・・ })
.done(function() {
 $.get('API2', function(){・・・})
   .done(function() {
・
・

このようにソースコードの可読性が悪くなります。各APIへの通信を並行で進めてもよい場合でも、順番に実行されるため、無駄な待ち時間が発生します。

このような問題を解決するため、jQuery1.5で追加された「$.Deferred」オブジェクトを使います。次のような特徴があります。

  • メソッドチェーンが可能
  • 複数のコールバックを登録管理できる
  • 成功・失敗の状態に応じた処理を行える

$.Deferred()ファクトリーメソッド

新しいDeferredオブジェクトを生成して返します。Deferredを使いたいときは、まず最初にこのメソッドを実行してDeferredオブジェクトを作成します。

書式

$.Deferred([前処理])
var dfd = $.Deferred();

promiseオブジェクトを返す

jQueryのDeferredは、非同期処理の標準のひとつであるPromises標準に準拠して設計されています。Deferredオブジェクトには、必ずpromiseオブジェクトが付属しています。

書式

dfd.poromise([ターゲット])
var dfd = $.Deferred();
dfd.promise();
<body>
    <div class="main">
      <div class="header">
        <h1> サンプル </h1>
      </div>
      <div class="content">
        <form>
          <button class="button" type="button" name="button">Deferred</button>
        </form>
        <div class="desc"></div>
      </div>
      <div class="footer">
        <hr>
        <p class="copyright">2024 xxxx all rights reserved.</p>
      </div>
    </div>
    <script>
      $(document).ready(function(){
        function getProducts(){
          var dfd = $.Deferred();
          $.get('http://localhost/sample.php',{'flg':1}, null, 'json').done(function(data){
            dfd.resolve(data);
          }).fail(function(){
            dfd.reject();
          });
          return dfd.promise();
        }
        $('button').on('click', function(){
          var promise = getProducts();
          promise.done(function(data){
            $('.desc').append(data.result);
          })
          .fail(function(data){
            $('.desc').append('APIコールに失敗しました。');
          });
        });
      });
    </script>
    </body>

sample.php

<?php
header("Access-Control-Allow-Origin:*");
$data = new stdClass();
$flg = filter_input(INPUT_GET,'flg');
$data->result = $flg == 1 ? '処理成功' : '処理失敗';
echo json_encode($data);

このスクリプトでは「Deferred」ボタンをクリックしたときにgetProducts()関数がコールされます。getProducts()の中では、$.Deferred()ファクトリーメソッドでDeferredオブジェクトを生成し、その後AjaxでAPI(sample.php)をコールします。Ajaxのコールバック関数では、成功したとき(.done)でDeferredオブジェクトの「resolve()」メソッドを実行し、失敗した場合(.fail)は「reject()」メソッドを実行します。最終的にgetProducts()関数はDeferredオブジェクトのpromise()メソッドをコールしてpromiseオブジェクトを返します。ボタンクリックのイベントハンドラに戻りましょう。getProducts()で受け取ったpromiseオブジェクトにも.done()や.fail()のメソッドがありますので、メソッドチェーンを使ってそれぞれの処理を行います。

promiseオブジェクトは、内部に「状態(state)」を持っています。生成された瞬間の状態は「pending(処理中で状態が確定していない)」です。正常終了時は「resolved(解決済み)」、エラーがあったときには「rejected(却下)」になります。あmた、promiseオブジェクト内にresolvedまたはrejectedステータスになったときに実行されるコールバックメソッドとして.done()及び.fail()が用意されています。少し複雑ですが、promiseオブジェクトは、非同期処理の実行にあたって「処理が完了したら結果を伝える」という約束の役目を、状態とそれに対するコールバックメソッドによって担っているわけです。Deferredオブジェクトのpromise()メソッドは、promiseオブジェクトを返すことで呼び出し元にその後の処理の手段を提供します。