コレクションを繰り返し、各要素に対してajax呼び出しを行う必要がある場合があります。次の要素に移動する前に各呼び出しを返すようにして、サーバーをリクエストで爆発させないようにします。これにより、他の問題が発生することがよくあります。そして、非同期をfalseに設定してブラウザをフリーズしたくありません。
通常、これには、成功コールバックごとにステップスルーする何らかのイテレータコンテキストの設定が含まれます。もっときれいで簡単な方法があるに違いないと思いますか?
誰もコレクションごとにきちんと動作する方法のための巧妙なデザインパターンを持っていますか?
$.ajaxQueue()
を使用するプラグインを開発しました _$.Deferred
_ 、 .queue()
、 $.ajax()
リクエストを完了すると解決される promise も返します。
_/*
* jQuery.ajaxQueue - A queue for ajax requests
*
* (c) 2011 Corey Frang
* Dual licensed under the MIT and GPL licenses.
*
* Requires jQuery 1.5+
*/
(function($) {
// jQuery on an empty object, we are going to use this as our Queue
var ajaxQueue = $({});
$.ajaxQueue = function( ajaxOpts ) {
var jqXHR,
dfd = $.Deferred(),
promise = dfd.promise();
// queue our ajax request
ajaxQueue.queue( doRequest );
// add the abort method
promise.abort = function( statusText ) {
// proxy abort to the jqXHR if it is active
if ( jqXHR ) {
return jqXHR.abort( statusText );
}
// if there wasn't already a jqXHR we need to remove from queue
var queue = ajaxQueue.queue(),
index = $.inArray( doRequest, queue );
if ( index > -1 ) {
queue.splice( index, 1 );
}
// and then reject the deferred
dfd.rejectWith( ajaxOpts.context || ajaxOpts,
[ promise, statusText, "" ] );
return promise;
};
// run the actual query
function doRequest( next ) {
jqXHR = $.ajax( ajaxOpts )
.done( dfd.resolve )
.fail( dfd.reject )
.then( next, next );
}
return promise;
};
})(jQuery);
_
JQuery 1.4を使用している場合、空のオブジェクトのアニメーションキューを利用して、要素に対するAjaxリクエスト用の独自の「キュー」を作成できます。
これを独自の$.ajax()
置換に含めることもできます。このプラグイン$.ajaxQueue()
は、jQueryの標準の 'fx'キューを使用します。キューがまだ実行されていない場合、最初に追加された要素を自動開始します。
_(function($) {
// jQuery on an empty object, we are going to use this as our Queue
var ajaxQueue = $({});
$.ajaxQueue = function(ajaxOpts) {
// hold the original complete function
var oldComplete = ajaxOpts.complete;
// queue our ajax request
ajaxQueue.queue(function(next) {
// create a complete callback to fire the next event in the queue
ajaxOpts.complete = function() {
// fire the original complete if it was there
if (oldComplete) oldComplete.apply(this, arguments);
next(); // run the next query in the queue
};
// run the query
$.ajax(ajaxOpts);
});
};
})(jQuery);
_
したがって、_<ul id="items">
_を(ajaxを使用して)_<li>
_にコピーする_<ul id="output">
_を持っています
_// get each item we want to copy
$("#items li").each(function(idx) {
// queue up an ajax request
$.ajaxQueue({
url: '/echo/html/',
data: {html : "["+idx+"] "+$(this).html()},
type: 'POST',
success: function(data) {
// Write to #output
$("#output").append($("<li>", { html: data }));
}
});
});
_
遅延約束を使用した迅速で小さなソリューション。これはjQueryの$.Deferred
、他のことを行う必要があります。
var Queue = function () {
var previous = new $.Deferred().resolve();
return function (fn, fail) {
return previous = previous.then(fn, fail || fn);
};
};
使用法、新しいキューを作成するための呼び出し:
var queue = Queue();
// Queue empty, will start immediately
queue(function () {
return $.get('/first');
});
// Will begin when the first has finished
queue(function() {
return $.get('/second');
});
例 を参照して、非同期リクエストを並べて比較します。
複雑さをすべて関数にラップして、次のような単純な呼び出しを行うことができます。
loadSequantially(['/a', '/a/b', 'a/b/c'], function() {alert('all loaded')});
以下は大まかなスケッチです(ajax呼び出しを除く実際の例)。これは、配列の代わりにキューのような構造を使用するように変更できます
// load sequentially the given array of URLs and call 'funCallback' when all's done
function loadSequantially(arrUrls, funCallback) {
var idx = 0;
// callback function that is called when individual ajax call is done
// internally calls next ajax URL in the sequence, or if there aren't any left,
// calls the final user specified callback function
var individualLoadCallback = function() {
if(++idx >= arrUrls.length) {
doCallback(arrUrls, funCallback);
}else {
loadInternal();
}
};
// makes the ajax call
var loadInternal = function() {
if(arrUrls.length > 0) {
ajaxCall(arrUrls[idx], individualLoadCallback);
}else {
doCallback(arrUrls, funCallback);
}
};
loadInternal();
};
// dummy function replace with actual ajax call
function ajaxCall(url, funCallBack) {
alert(url)
funCallBack();
};
// final callback when everything's loaded
function doCallback(arrUrls, func) {
try {
func();
}catch(err) {
// handle errors
}
};
サーバーからのすべてのコールバックが同じコルーチンを呼び出すことができるように、複数のエントリポイントを持つコルーチンが理想的です。くそー、これはJavascript 1.7で実装されようとしています。
クロージャを使用してみましょう...
function BlockingAjaxCall (URL,arr,AjaxCall,OriginalCallBack)
{
var nextindex = function()
{
var i =0;
return function()
{
return i++;
}
};
var AjaxCallRecursive = function(){
var currentindex = nextindex();
AjaxCall
(
URL,
arr[currentindex],
function()
{
OriginalCallBack();
if (currentindex < arr.length)
{
AjaxCallRecursive();
}
}
);
};
AjaxCallRecursive();
}
// suppose you always call Ajax like AjaxCall(URL,element,callback) you will do it this way
BlockingAjaxCall(URL,myArray,AjaxCall,CallBack);
ええ、他の答えは機能しますが、多くのコードで面倒です。 Frame.jsは、この状況にエレガントに対処するために設計されました。 https://github.com/bishopZ/Frame.js
たとえば、これによりほとんどのブラウザがハングします:
for(var i=0; i<1000; i++){
$.ajax('myserver.api', { data:i, type:'post' });
}
これはしませんが:
for(var i=0; i<1000; i++){
Frame(function(callback){
$.ajax('myserver.api', { data:i, type:'post', complete:callback });
});
}
Frame.start();
また、Frameを使用すると、一連のAJAXリクエストが完了した後(必要な場合)に応答オブジェクトをウォーターフォールしてすべて処理することができます。
var listOfAjaxObjects = [ {}, {}, ... ]; // an array of objects for $.ajax
$.each(listOfAjaxObjects, function(i, item){
Frame(function(nextFrame){
item.complete = function(response){
// do stuff with this response or wait until end
nextFrame(response); // ajax response objects will waterfall to the next Frame()
$.ajax(item);
});
});
Frame(function(callback){ // runs after all the AJAX requests have returned
var ajaxResponses = [];
$.each(arguments, function(i, arg){
if(i!==0){ // the first argument is always the callback function
ajaxResponses.Push(arg);
}
});
// do stuff with the responses from your AJAX requests
// if an AJAX request returned an error, the error object will be present in place of the response object
callback();
});
Frame.start()
同じシナリオでいくつかの簡単な解決策を探して、将来的に他の人を助けるかもしれないと考えてこの回答を投稿しています。
これは、ES6で導入されたネイティブpromiseサポートを使用しても可能になりました。 ajax呼び出しをプロミスでラップし、要素のハンドラーに返すことができます。
function ajaxPromise(elInfo) {
return new Promise(function (resolve, reject) {
//Do anything as desired with the elInfo passed as parameter
$.ajax({
type: "POST",
url: '/someurl/',
data: {data: "somedata" + elInfo},
success: function (data) {
//Do anything as desired with the data received from the server,
//and then resolve the promise
resolve();
},
error: function (err) {
reject(err);
},
async: true
});
});
}
次に、要素のコレクションがある場所から関数を再帰的に呼び出します。
function callAjaxSynchronous(elCollection) {
if (elCollection.length > 0) {
var el = elCollection.shift();
ajaxPromise(el)
.then(function () {
callAjaxSynchronous(elCollection);
})
.catch(function (err) {
//Abort further ajax calls/continue with the rest
//callAjaxSynchronous(elCollection);
});
}
else {
return false;
}
}
http://developer.yahoo.com/yui/3/io/#queue を使用して、その機能を取得します。
あなたが言うように、私が思いつくことができる唯一の解決策は、保留中のコール/コールバックのリストを維持することです。または、前のコールバックで次の呼び出しをネストしますが、少し面倒です。
then
を使用して同じことを実現できます。
var files = [
'example.txt',
'example2.txt',
'example.txt',
'example2.txt',
'example.txt',
'example2.txt',
'example2.txt',
'example.txt'
];
nextFile().done(function(){
console.log("done",arguments)
});
function nextFile(text){
var file = files.shift();
if(text)
$('body').append(text + '<br/>');
if(file)
return $.get(file).then(nextFile);
}
さまざまなケースで再利用可能な、もう少し洗練されたアプローチをお勧めします。
たとえば、ユーザーがテキストエディターで入力しているときに呼び出しシーケンスを遅くする必要がある場合に使用します。
ただし、コレクションを反復処理するときにも機能するはずです。この場合、リクエストをキューに入れて、12の代わりに単一のAJAX呼び出しを送信できます。
queueing = {
callTimeout: undefined,
callTimeoutDelayTime: 1000,
callTimeoutMaxQueueSize: 12,
callTimeoutCurrentQueueSize: 0,
queueCall: function (theCall) {
clearTimeout(this.callTimeout);
if (this.callTimeoutCurrentQueueSize >= this.callTimeoutMaxQueueSize) {
theCall();
this.callTimeoutCurrentQueueSize = 0;
} else {
var _self = this;
this.callTimeout = setTimeout(function () {
theCall();
_self.callTimeoutCurrentQueueSize = 0;
}, this.callTimeoutDelayTime);
}
this.callTimeoutCurrentQueueSize++;
}
}