jQuery deferredはどのように使用できますか?


279

jQuery 1.5は、新しいDeferredオブジェクトと付属のメソッド.whenを提供.Deferred._Deferredます。

.Deferred以前に使用したことがない人のために、そのソースに注釈を付けました

これらの新しいメソッドの可能な使用法は何ですか?それらをパターンにどのように合わせるのですか?

私はすでにAPIソースを読みましたので、それが何をするか知っています。私の質問は、これらの新機能を日常のコードでどのように使用できるかです。

AJAXリクエストを順番に呼び出すバッファクラスの簡単ながあります。(前の1つが終了した後、次の1つの開始)。

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

私はデモとの可能な用途を探しています.Deferred.when

の例を見るのも素敵です._Deferred

jQuery.ajax例の新しいソースへのリンクは不正行為です。

私は、操作が同期的に行われるか非同期的に行われるかを抽象化するときに利用できる手法に特に興味があります。


19
FAQから:主観的な質問をするのは避けてください...すべての回答が等しく有効です:「あなたの好きな______は何ですか?」(彼らの強調)
TJクラウダー

2
@TJCrowser言い換えます。
レイノス

5
それは良い質問ですが、そこにはできませんという :-)に答えることができる多くの人々
先のとがっ

2
@Pointy主にサードパーティのプラグインだったときにそれを使った人たちを見ていた。そして、人々に座ってそれを使用することを奨励します!
レイノス

1
._Deferred単に使用する真の「据え置きオブジェクト」.Deferredです。これは、おそらく必要になることのない内部オブジェクトです。
David Tang

回答:


212

私が考えることができる最良のユースケースは、AJAX応答をキャッシュすることです。これは、トピックに関するRebecca Murpheyの紹介記事の変更例です。

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

基本的に、値がキャッシュからすぐに返される前に、一度要求された場合。それ以外の場合、AJAX要求はデータをフェッチしてキャッシュに追加します。の$.when/ .thenこのいずれかを気にしません。気にする必要があるのは.then()、どちらの場合もハンドラーに渡される応答を使用することだけです。jQuery.when()Promise / Deferred以外をCompleted / Deferredとして処理し、任意のチェーン.done()または.then()チェーンですぐに実行します。

Deferredは、タスクが非同期で動作する場合と動作しない場合があり、コードからその条件を抽象化したい場合に最適です。

$.whenヘルパーを使用した別の実例:

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});

4
2つの見事な例。私は2番目のものに似たものを実装しましたが、4つのajaxリクエストを使用しましたが、はるかに読みやすく、コンパクトで、ロジック、保守性などに優れています。jQuery.Deferredは本当に良いものです。
PJP 2011

5
これは、このトピックに関する有用なビデオです。bigbinary.com/ videos / 3
Nick Vanderbilt

5
結果が偽の値の場合、キャッシュは機能しません。また、取得したブランチに応じてgetDataが2つの異なるタイプを返すという事実も好きではありません。
Marko Dumic 2012年

3
ajaxキャッシングのより良い実装については、以下のJulian D.の回答を参照してください。
event_jr

1
私は最初のコード例がどのように機能するかさえ理解していません:オブジェクトがキャッシュされない場合を理解していますが、それがcache[ val ]プロミスを返さない場合(jqueryのドキュメントでは、パラメーターは送信者から返されたデータであると述べています)を意味します.thenwillエラーのメンバーアクセス...そうですか?何が欠けていますか?
chacham15 14

79

ehyndの回答のように、AJAXキャッシュの実装が少し異なります

で述べたようにfortuneRiceのフォローアップの質問それらの一つが戻ってきた前に要求が行われた場合、ehyndの実装は、実際に複数の同一の要求を防ぐことはできませんでした。あれは、

for (var i=0; i<3; i++) {
    getData("xxx");
}

「xxx」の結果がまだキャッシュされていない場合、おそらく3つのAJAXリクエストが発生します。

これは、結果の代わりにリクエストのDeferredをキャッシュすることで解決できます。

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

1
初回のフェッチでキャッシュをクリア/更新することはないため、これはまだ完璧ではないと思います。これにより、AJAX呼び出しが更新で機能しなくなります。
zyzyis 14

45

mutexの代わりにdeferredを使用できます。これは基本的に、複数のajaxの使用シナリオと同じです。

ミューテックス

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

延期

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

Deferredをミューテックスとしてのみ使用する場合は、パフォーマンスへの影響に注意してください(http://jsperf.com/deferred-vs-mutex/2)。Deferredによって提供される利便性と追加の利点はそれだけの価値がありますが、実際の(ユーザー主導のイベントベースの)使用では、パフォーマンスへの影響は目立ちません。


私がこれを見つけるのは驚くほど困難でした。解決されたpromiseを返すsetIntervalを含む関数でそれを使用し、divの幅が特定の数を超えると自己破壊されるようにしました。問題を解決できなかった場合のトラブルシューティングと解決策でしたが、私はそれについて有頂天です。
JSG


20

私が良い目的に使用しているもう1つの用途は、複数のソースからデータをフェッチすることです。以下の例では、クライアントとRESTサーバー間の検証のために、既存のアプリケーションで使用されている複数の独立したJSONスキーマオブジェクトをフェッチしています。この場合、すべてのスキーマが読み込まれる前に、ブラウザー側のアプリケーションがデータの読み込みを開始しないようにします。$ .when.apply()。then()はこれに最適です。エラー状態を監視するためにthen(fn1、fn2)を使用する際の指針については、レイノスに感謝します。

fetch_sources = function (schema_urls) {
    var fetch_one = function (url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json",
            });
        }
    return $.map(schema_urls, fetch_one);
}

var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(

function () {
    var schemas = $.map(arguments, function (a) {
        return a[0]
    });
    start_application(schemas);
}, function () {
    console.log("FAIL", this, arguments);
});     

10

Deferredsを使用してあらゆる種類の計算用のキャッシュを実装する別の例(通常、パフォーマンスを重視するタスクまたは長時間実行されるタスク):

var ResultsCache = function(computationFunction, cacheKeyGenerator) {
    this._cache = {};
    this._computationFunction = computationFunction;
    if (cacheKeyGenerator)
        this._cacheKeyGenerator = cacheKeyGenerator;
};

ResultsCache.prototype.compute = function() {
    // try to retrieve computation from cache
    var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
    var promise = this._cache[cacheKey];

    // if not yet cached: start computation and store promise in cache 
    if (!promise) {
        var deferred = $.Deferred();
        promise = deferred.promise();
        this._cache[cacheKey] = promise;

        // perform the computation
        var args = Array.prototype.slice.call(arguments);
        args.push(deferred.resolve);
        this._computationFunction.apply(null, args);
    }

    return promise;
};

// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
    return Array.prototype.slice.call(arguments).join("|");
};

このクラスを使用して(シミュレートされた重い)計算を実行する例を次に示します。

// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
    console.log("Performing computation: adding " + a + " and " + b);
    // simulate rather long calculation time by using a 1s timeout
    setTimeout(function() {
        var result = a + b;
        resultHandler(result);
    }, 1000);
});

addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

addingMachine.compute(1, 1).then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

同じ基礎となるキャッシュを使用して、Ajaxリクエストをキャッシュできます。

var ajaxCache = new ResultsCache(function(id, resultHandler) {
    console.log("Performing Ajax request for id '" + id + "'");
    $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
        resultHandler(data.value);
    });
});

ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

ajaxCache.compute("anotherID").then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

このjsFiddleで上記のコードで遊ぶことができます。


9

1)これを使用して、コールバックの順序どおりの実行を保証します。

var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });

step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.

step2.resolve();
step3.resolve();
step1.resolve();

2)これを使用して、アプリのステータスを確認します。

var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});

2

遅延オブジェクトを使用して、Webkitブラウザーで適切に機能する流動的なデザインを作成できます。Webkitブラウザーは、ウィンドウがサイズ変更されるピクセルごとにサイズ変更イベントを起動します。これは、サイズ変更ごとに1回だけイベントを起動するFFおよびIEとは異なります。その結果、ウィンドウのサイズ変更イベントにバインドされた関数が実行される順序を制御できません。このような何かが問題を解決します:

var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();

function resizeAlgorithm() {
//some resize code here
}

$(window).resize(function() {
    resizeQueue.done(resizeAlgorithm);
});

これにより、コードの実行がシリアル化され、意図したとおりに実行されます。オブジェクトメソッドをコールバックとして遅延オブジェクトに渡すときの落とし穴に注意してください。そのようなメソッドがdeferredへのコールバックとして実行されると、「this」参照はdeferredオブジェクトへの参照で上書きされ、メソッドが属するオブジェクトを参照しなくなります。


これはどのようにシリアル化を行いますか?キューはすでに解決されているためresizeQueue.done(resizeAlgorithm)、とまったく同じresizeAlgorithmです。それは完全な偽物です!
レイノス

resizeAlgorithmのコードが複雑な場合、ウィンドウのサイズを変更するピクセルごとに関数が呼び出されると、webkitのJavaScript実装は同期を失います。Deferredは、コールバックをキューに保持し、FIFO順に実行します。したがって、「完了」コールバックを追加し、遅延が既に解決されているためにすぐに実行される場合、最初のコールバックの実行中に遅延に追加された別の「完了」コールバックがキューに追加され、待機する必要があります最初に返されるコールバック用。これがあなたの質問に答えてくれることを願っています。
ミロシュラシッチ

ブラウザのJSインタプリタはシングルスレッドです。resizeAlgorithmに非同期コードが含まれていない限り、次の呼び出し.doneが行われる前に、関数全体が動作を終了しているはずです。
レイノス

@Raynos:私はそれを知っていますが、私は単にresizeAlgorithmをresizeで呼び出すように試みました、そしてそれは他のもので完全に働いている間ウェブキットブラウザで空白の白いページを与えます。遅延オブジェクトはこの問題を解決します。私はこれについてより深い研究をするのに十分な時間を持っていませんでした。Webkitのバグである可能性があります。resizeAlgorithmに非同期コードがある場合、私の例で使用されているdeferredは役に立たないと思います。
ミロシュRašić

2
スロットル/デバウンスプラグインbenalman.com/projects/jquery-throttle-debounce-pluginのようなものを使用して、サイズ変更ごとに1回以上関数が発火しないようにするべきではありません。
wheresrhys

2

また、JQueryを利用するサードパーティのライブラリと統合することもできます。

そのようなライブラリの1つがバックボーンです。これは、実際には次のバージョンでDeferredをサポートする予定です。


2
read more here代わりに使用しon my blogます。それはより良い習慣であり、(誤って)スパムにさらされることから答えを救うことができます。:)
Lokesh Mehra

1

実際のコードでDeferredを使用しました。プロジェクトjQueryターミナルには、ユーザーが定義したコマンド(ユーザーが入力してEnterキーを押したような)を呼び出す関数execがあり、DeferredをAPIに追加し、配列を使用してexecを呼び出しています。このような:

terminal.exec('command').then(function() {
   terminal.echo('command finished');
});

または

terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
   terminal.echo('all commands finished');
});

コマンドは非同期コードを実行でき、execは順番にユーザーコードを呼び出す必要があります。私の最初のAPIは、一時停止/再開の呼び出しのペアを使用し、新しいAPIでは、ユーザーがプロミスを返すときにそれらを自動で呼び出します。したがって、ユーザーコードは

return $.get('/some/url');

または

var d = new $.Deferred();
setTimeout(function() {
    d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();

私はこのようなコードを使用します:

exec: function(command, silent, deferred) {
    var d;
    if ($.isArray(command)) {
        return $.when.apply($, $.map(command, function(command) {
            return self.exec(command, silent);
        }));
    }
    // both commands executed here (resume will call Term::exec)
    if (paused) {
        // delay command multiple time
        d = deferred || new $.Deferred();
        dalyed_commands.push([command, silent, d]);
        return d.promise();
    } else {
        // commands may return promise from user code
        // it will resolve exec promise when user promise
        // is resolved
        var ret = commands(command, silent, true, deferred);
        if (!ret) {
            if (deferred) {
                deferred.resolve(self);
                return deferred.promise();
            } else {
                d = new $.Deferred();
                ret = d.promise();
                ret.resolve();
            }
        }
        return ret;
    }
},

dalyed_commandsは、すべてのdalyed_commandsでexecを再度呼び出す再開関数で使用されます。

コマンド機能の一部(関連しない部分を削除しました)

function commands(command, silent, exec, deferred) {

    var position = lines.length-1;
    // Call user interpreter function
    var result = interpreter.interpreter(command, self);
    // user code can return a promise
    if (result != undefined) {
        // new API - auto pause/resume when using promises
        self.pause();
        return $.when(result).then(function(result) {
            // don't echo result if user echo something
            if (result && position === lines.length-1) {
                display_object(result);
            }
            // resolve promise from exec. This will fire
            // code if used terminal::exec('command').then
            if (deferred) {
                deferred.resolve();
            }
            self.resume();
        });
    }
    // this is old API
    // if command call pause - wait until resume
    if (paused) {
        self.bind('resume.command', function() {
            // exec with resume/pause in user code
            if (deferred) {
                deferred.resolve();
            }
            self.unbind('resume.command');
        });
    } else {
        // this should not happen
        if (deferred) {
            deferred.resolve();
        }
    }
}

1

ehyndsによる応答は、応答データをキャッシュするため機能しません。それは約束でもあるjqXHRをキャッシュするべきです。正しいコードは次のとおりです。

var cache = {};

function getData( val ){

    // return either the cached value or an
    // jqXHR object (which contains a promise)
    return cache[ val ] || $.ajax('/foo/', {
        data: { value: val },
        dataType: 'json',
        success: function(data, textStatus, jqXHR){
            cache[ val ] = jqXHR;
        }
    });
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

Julian D.の答えは正しく機能し、より良い解決策です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.