jQuery 1.5は新しいDeferredオブジェクトと付属のメソッド .when
、 .Deferred
と ._Deferred
をもたらします。
以前に.Deferred
を使ったことがない人のために、私は そのための情報源 と注釈を付けました。
これらの新しい方法の可能な使用法は何ですか、どのようにしてそれらをパターンに適合させるのですか?
私はすでに API と source を読んだので、それが何をするのか知っています。私の質問は、日常のコードでこれらの新機能をどのように使用できるかです。
単純な 例 を順番にAJAX requestを呼び出すバッファクラスがあります。 (前のものが終わった後、次のものが始まります)。
/* 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
ソースへのリンクは不正です。
私は、操作が同期的に行われるのか非同期的に行われるのかを抽象化するときにどのような技術が利用可能であるかに特に興味があります。
私が考えることができる最良のユースケースは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.
});
基本的に、キャッシュからすぐに値が返される前に値がすでに1回要求されている場合。それ以外の場合、AJAX要求はデータを取得してそれをキャッシュに追加します。 $.when
/.then
はこれのどれでも構いません。気にする必要があるのは、どちらの場合も.then()
ハンドラーに渡されるレスポンスを使用することだけです。 jQuery.when()
は、Promise/DeferredをCompletedとして処理し、チェーン上の.done()
または.then()
を直ちに実行します。
タスクが非同期的に動作する場合と非同期で動作する場合があり、コードからその条件を抽象化したい場合には、延期が最適です。
$.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
});
これは、 ehynd's answer のように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.
});
ミューテックスの代わりに遅延を使用できます。これは、複数のAjax使用シナリオと基本的に同じです。
MUTEX
var mutex = 2;
setTimeout(function() {
callback();
}, 800);
setTimeout(function() {
callback();
}, 500);
function callback() {
if (--mutex === 0) {
//run code
}
}
DEFERRED
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によって提供される追加の利点も価値がありますが、実際の(ユーザー主導のイベントベースの)使用法では、パフォーマンスへの影響は顕著ではありません。
これは自己宣伝的な答えですが、私はこれを調査するために数ヶ月を費やし、その結果をjQuery Conference San Francisco 2012で発表しました。
これが講演の無料ビデオです。
http://www.confreaks.com/videos/993-jqcon2012-i-promise-to-show-you-when-to-use-deferreds
もう1つの目的は、複数のソースからデータを取得することです。以下の例では、クライアントとRESTサーバー間の検証のために既存のアプリケーションで使用される複数の独立したJSONスキーマオブジェクトを取得しています。この場合、すべてのスキーマがロードされる前に、ブラウザ側のアプリケーションにデータのロードを開始させたくありません。 $ .when.apply()。then()はこれに最適です。エラー状態を監視するためにthen(fn1、fn2)を使用するためのポインタを提供してくれたRaynosに感謝します。
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);
});
Deferred
sを使用してあらゆる種類の計算用のキャッシュを実装する別の例(通常はパフォーマンスを重視するタスクまたは長時間実行されるタスク):
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);
});
上記のコードは this jsFiddle で遊ぶことができます。
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
});
遅延オブジェクトを使用すると、Webkitブラウザでうまく機能する流動的なデザインを作成できます。 FFとIEとは異なり、Webkitブラウザはウィンドウがリサイズされるピクセルごとにサイズ変更イベントを発生させます。結果として、ウィンドウのサイズ変更イベントにバインドされた関数が実行される順序を制御することはできません。このような何かが問題を解決します。
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'参照はその遅延オブジェクトを参照して上書きされ、そのメソッドが属するオブジェクトを参照しなくなります。
JQueryを利用するサードパーティのライブラリと統合することもできます。
そのようなライブラリの1つにBackboneがあります。これは実際にはDeferredを次のバージョンでサポートする予定です。私はそれについても話しました blog
実際のコードではDeferredを使ったばかりです。 project jQuery Terminal ユーザが定義したコマンドを呼び出す関数exec(彼が入力してEnterキーを押すなど)を持っていますが、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();
}
}
}
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.の答えは正しく機能し、より良い解決策です。