jQuery.Deferredオブジェクト

54173 ワード

——edited by李家優
原文住所:http://javascript.ruanyifeng.com/jquery/deferred.html
概要
deferredオブジェクトはjQuery対Promisesインタフェースの実装である.簡単に言えば、Promisesは非同期操作の汎用インタフェースであり、エージェント(proxy)の役割を果たし、非同期操作を同期操作特性を持つ特殊なオブジェクトにパッケージする.非同期操作の典型的な例は、Ajax操作、ウェブアニメーション、ウェブワークなどである.
jQueryのすべてのAjax操作関数は、デフォルトではdeferredオブジェクトを返します.
Promisesって何?
JavaScriptシングルスレッドの特徴で、ある操作に時間がかかる場合は、他の操作はキューに並んで待つ必要があります.プログラム全体が応答を失うことを避けるために、通常の解決策は、それらを後ろに並べた操作を「コールバック関数」(callback)の形式に書くことである.これは問題を解決することができますが、いくつかの顕著な欠点があります.
コールバック関数は往々にして関数パラメータの形式に書かれ、関数の入力と出力が非常に混乱し、プログラム全体の読解性が悪い.
コールバック関数は1つしか指定できないことが多く、複数の操作がある場合はコールバック関数を書き換える必要があります.
プログラム全体の実行プロセスが乱され、エラーの除去とデバッグの難易度が増加します.
Promisesはこれらの問題を解決するために提案され,その主な目的はコールバック関数に取って代わり,非同期動作の解決策となることである.その核心思想は、非同期操作を1つのオブジェクトに戻すことであり、他の操作はこのオブジェクトに対して完了する.たとえばajax操作はPromiseオブジェクトを返すと仮定します.
var promise = get('http://www.example.com');

その後、Promiseオブジェクトには、コールバック関数を指定するthenメソッドがあります.非同期操作が完了すると、指定したコールバック関数が呼び出されます.
promise.then(function (content) {
  console.log(content)
})

上記の2つのコードを統合することで、プログラムの流れがより明確に見えます.
get('http://www.example.com').then(function (content) {
  console.log(content)
})

バージョン1.7より前に、jQueryのAjax操作はコールバック関数を採用した.
$.ajax({
    url:"/echo/json/",
    success: function(response)
    {
       console.info(response.name);
    }
});

1.7以降、Ajax操作はPromiseオブジェクトに直接戻ります.これはthenメソッドでコールバック関数を指定できることを意味します.
$.ajax({
    url: "/echo/json/",
}).then(function (response) {
    console.info(response.name);
});

deferredオブジェクトのメソッド
$.deferred()メソッド
役割はdeferredオブジェクトを生成することです.
var deferred = $.deferred();

done()とfail()
この2つの方法は、コールバック関数をバインドするために使用されます.done()は非同期操作に成功したコールバック関数を指定し、fail()は失敗したコールバック関数を指定します.
var deferred = $.Deferred();

deferred.done(function(value) {
   alert(value);
});

それらは元のdeferredオブジェクトを返すので、チェーン式の書き方を採用し、doneとfailを含む別の方法を後でリンクすることができます.
resolve()とreject()
この2つの方法はdeferredオブジェクトの状態を変更するために使用されます.resolve()ステータスを非同期操作に変更できました.reject()を操作に変更できませんでした.
var deferred = $.Deferred();

deferred.done(function(value) {
   alert(value);
});

deferred.resolve("hello world");

resolve()を呼び出すとdone()メソッドで指定したコールバック関数が順次実行されます.reject()が呼び出されると、fail()メソッドとthen()メソッドで指定されたコールバック関数が順次実行されます.
stateメソッド
このメソッドはdeferredオブジェクトの現在の状態を返すために使用されます.
var deferred = new $.Deferred();
deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"

メソッドの戻り値は3つあります.
pending:操作がまだ完了していないことを示します.
resolved:操作が成功したことを示します.
rejected:操作に失敗したことを示します.
notify()とprogress()
progress()は、notify()メソッドを呼び出すと実行されるコールバック関数を指定します.非同期操作の実行中に、定期的に進捗バーの進捗状況を返すなど、いくつかの操作を実行できるようにするインタフェースを提供することを目的としています.
   var userProgress = $.Deferred();
    var $profileFields = $("input");
    var totalFields = $profileFields.length
        
    userProgress.progress(function (filledFields) {
        var pctComplete = (filledFields/totalFields)*100;
        $("#progress").html(pctComplete.toFixed(0));
    }); 

    userProgress.done(function () {
        $("#thanks").html("Thanks for completing your profile!").show();
    });
    
    $("input").on("change", function () {
        var filledFields = $profileFields.filter("[value!='']").length;
        userProgress.notify(filledFields);
        if (filledFields == totalFields) {
            userProgress.resolve();
        }
    });

then()
then()の役割もコールバック関数を指定し、3つのパラメータ、すなわち3つのコールバック関数を受け入れることができる.1番目のパラメータはresolve時に呼び出されるコールバック関数で、2番目のパラメータはreject時に呼び出されるコールバック関数で、3番目のパラメータはprogress()メソッドで呼び出されるコールバック関数です.
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

jQuery 1.8の前にthen()はただ.done().fail()の書き方の文法糖は、2つの書き方が等価です.jQuery 1.8の後、then()は新しいdeferredオブジェクトを返し、done()は元のdeferredオブジェクトを返します.then()で指定したコールバック関数に戻り値がある場合、その戻り値はパラメータとして後続のコールバック関数に渡されます.
var defer = jQuery.Deferred();

defer.done(function(a,b){
            return a * b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a * b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 2, 3 );

jQuery 1.8リリースの前に、上記のコードの結果は次のとおりです.
result = 2 
result = 2 
result = 2 

jQuery 1.8以降、返される結果は
result = 2 
result = 6 
result = NaN 

この点は特に注意しなければならない.
$.ajax( url1, { dataType: "json" } )
.then(function( data ) {
    return $.ajax( url2, { data: { user: data.userId } } );
}).done(function( data ) {
  //  url2     
});

上のコードの最後のdoneメソッドは、url 1から取得したデータではなくurl 2から取得したデータを処理します.
then()を使用すると、戻り値という特性が変更され、他のコールバック関数を呼び出す前に、前の操作で返された値を処理できます.
var post = $.post("/echo/json/")
    .then(function(p){
        return p.firstName;
    });

post.done(function(r){ console.log(r); });

上のコードはthen()メソッドを使用して、返されたデータから必要なフィールド(firstName)を取り出すので、後の操作でこのフィールドのみを処理できます.
場合によっては、Ajax操作はjson文字列にerror属性を返し、エラーが発生したことを示します.この場合,従来の方法はdone()によって誤りが発生したか否かを判断するしかない.then()メソッドにより、deferredオブジェクトにfail()メソッドを呼び出すことができます.
var myDeferred = $.post('/echo/json/', {json:JSON.stringify({'error':true})})
    .then(function (response) {
            if (response.error) {
                return $.Deferred().reject(response);
            }
            return response;
        },function () {
            return $.Deferred().reject({error:true});
        }
    );

myDeferred.done(function (response) {
        $("#status").html("Success!");
    }).fail(function (response) {
        $("#status").html("An error occurred");
    });

上記のコードでは、通信エラーにかかわらず、サーバがエラーを返すと、rejectメソッドが呼び出され、新しいdeferredオブジェクトが返されます.ステータスはrejectedなので、failメソッドで指定されたコールバック関数がトリガーされます.
errorの処理については,jQueryのdeferredオブジェクトが他のPromises仕様を実装する関数ライブラリと大きく異なる.つまり、deferredオブジェクトの実行中にPromisesオブジェクト以外のエラーが投げ出されると、後続のthenメソッドで指定されたrejectedコールバック関数によってキャプチャされず、アプリケーションレベルに伝播し続け、他の関数ライブラリでこのエラーがキャプチャされます.コード動作がPromises仕様と一致するように、エラーが発生した場合は、常にrejectメソッドを使用してエラーを返します.
always()
Always()もコールバック関数を指定し、resolveまたはrejectにかかわらず呼び出されます.
pipeメソッド
pipeメソッドは、thenメソッド、doneメソッド、failメソッド、alwaysメソッドで指定されたコールバック関数を呼び出す前にpipeメソッドで指定されたコールバック関数を実行することを示す関数をパラメータとして受け入れます.通常、サーバから返されるデータを初歩的に処理するために使用されます.
promiseオブジェクト
ほとんどの場合、deferredオブジェクトの状態を外部から変更させたくない.この場合、deferredオブジェクトに基づいてpromiseオブジェクトを返すことができます.後者は、promiseがdeferredの読み取り専用版であるか、promiseが完了するタスクに対する約束であることをより通俗的に理解することができる.
promiseオブジェクトを使用して、元のdeferredオブジェクトにコールバック関数を追加し、ステータスをクエリーできますが、ステータスを変更することはできません.つまり、promiseオブジェクトではresolveメソッドとrejectメソッドを呼び出すことはできません.
function getPromise(){
    return $.Deferred().promise();
}

try{
    getPromise().resolve("a");
} catch(err) {
    console.log(err);
}

上のコードでエラーが発生し、TypeError{}が表示されます.
jQueryのajax()メソッドはpromiseオブジェクトを返します.また、Animationクラス操作ではpromiseオブジェクトを使用することもできます.
var promise = $('div.alert').fadeIn().promise();

$.when()メソッド
$.when()は、複数のdeferredオブジェクトをパラメータとして受け入れ、それらがすべて実行に成功した後、resolved状態のコールバック関数を呼び出すが、そのうちの1つが失敗した場合、rejected状態のコールバック関数を呼び出す.これは、複数の非同期操作を1つに統合することに相当します.
$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(successFunc, failureFunc);

上記のコードは、thenメソッドで指定されたコールバック関数を実行するには、3つのajax操作が終了するまで待たなければならないことを示しています.
whenメソッドで何個の操作を実行するか、コールバック関数には何個のパラメータがあり、前の各操作の戻り結果に対応します.
$.when(
    $.ajax( "/main.php" ),
    $.ajax( "/modules.php" ),
    $.ajax( "/lists.php" )
).then(function (resp1, resp2, resp3){
    console.log(resp1);
    console.log(resp2);
    console.log(resp3);
});

上のコードのコールバック関数には3つのパラメータがあり、resp 1、resp 2、resp 3は、前の3つのajax操作の戻り結果に順次対応しています.
whenメソッドのもう1つの役割は、パラメータがDeferredオブジェクトまたはPromiseオブジェクトでない場合、whenメソッドのコールバック関数がすぐに実行されることです.
$.when({testing: 123}).done(function (x){
  console.log(x.testing); // "123"
});

上のコードで指定したコールバック関数は、whenメソッドの直後に実行されます.
この特徴を用いて,キャッシュ効果のある非同期操作関数を書くことができる.すなわち,この関数を初めて呼び出すと非同期操作が実行され,後でこの関数を呼び出すとキャッシュの結果が返される.
function maybeAsync( num ) {
  var dfd = $.Deferred();

  if ( num === 1 ) {
    setTimeout(function() {
      dfd.resolve( num );
    }, 100);
    return dfd.promise();
  }

  return num;
}

$.when(maybeAsync(1)).then(function (resp){
  $('#target').append('

'

+ resp + ''); }); $.when(maybeAsync(0)).then(function (resp){ $('#target').append( '

'

+ resp + ''); });

上のコードは、maybeAsync関数のパラメータが1の場合、非同期操作が実行され、そうでない場合、キャッシュの結果がすぐに返されることを示しています.
≪インスタンス|Instance|emdw≫
waitメソッド
deferredオブジェクトでwaitメソッドを書くことができ、何ミリ秒待ってから実行するかを示します.
$.wait = function(time) {
  return $.Deferred(function(dfd) {
    setTimeout(dfd.resolve, time);
  });
}

使用方法は次のとおりです.
$.wait(5000).then(function() {
  alert("Hello from the future!");
});

上書きsettimeoutメソッド
上記のwaitメソッドに基づいて、settimeoutメソッドを書き換えてdeferredオブジェクトを返すこともできます.
function doSomethingLater(fn, time) {
  var dfd = $.Deferred();
  setTimeout(function() {
    dfd.resolve(fn());
  }, time || 0);
  return dfd.promise();
}

var promise = doSomethingLater(function (){
  console.log( '      ' );
}, 100);

カスタム操作deferredインタフェースの使用
deferredインタフェースを使用して、任意の操作でdone()とfail()でコールバック関数を指定できるようにします.
Twitter = {
  search:function(query) {
    var dfr = $.Deferred();
    $.ajax({
     url:"http://search.twitter.com/search.json",
     data:{q:query},
     dataType:'jsonp',
     success:dfr.resolve
    });
    return dfr.promise();
  }
}

使用方法は次のとおりです.
Twitter.search('intridea').then(function(data) {
  alert(data.results[0].text);
});

deferredオブジェクトのもう一つの利点は、複数のコールバック関数を追加できることです.
function doSomething(arg) {
  var dfr = $.Deferred();
  setTimeout(function() {
    dfr.reject("Sorry, something went wrong.");
  });
  return dfr;
}

doSomething("uh oh").done(function() {
  alert("Won't happen, we're erroring here!");
}).fail(function(message) {
  alert(message)
});

画像認識図のQRコードを長く押す(または、微信の公衆番号FrontEndStoryを検索する)ことで、「フロントエンドのこと」に注目し、最新のフロントエンド技術を知ることができます.