How to chain ajax requests? - javascript

I have to interact with a remote api that forces me to chain requests. Thats a callback-hell in asynchronous mode:
// pseudocode: ajax(request_object, callback)
ajax(a, function() {
ajax(b(a.somedata), function() {
ajax(c(b.somedata), function() {
c.finish()
}
})
})
It would be much more readable in sync mode:
sjax(a)
sjax(b(a.somedata))
sjax(c(b.somedata))
c.finish()
But Sjax is evil :) How do I do that in a nice not-so-evil and readable way?

You could have a single function which is passed an integer to state what step the request is in, then use a switch statement to figure out what request needs to be make next:
function ajaxQueue(step) {
switch(step) {
case 0: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { ajaxQueue(1); }
}); break;
case 1: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { ajaxQueue(2); }
}); break;
case 2: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { alert('Done!'); }
}); break;
}
}
ajaxQueue(0);
Hope that helps!

Don't use anonymous functions. Give them names. I don't know if you're able to do what I wrote below though:
var step_3 = function() {
c.finish();
};
var step_2 = function(c, b) {
ajax(c(b.somedata), step_3);
};
var step_1 = function(b, a) {
ajax(b(a.somedata), step_2);
};
ajax(a, step_1);

This function should chain together a list of ajax requests, if the callbacks always return the parameters necessary for the next request:
function chainajax(params, callbacks) {
var cb = shift(callbacks);
params.complete = function() {
var newparams = cb(arguments);
if (callbacks)
chainajax(newparams, callbacks);
};
$.ajax(params);
};
You can define these callback functions separately and then chain them together:
function a(data) {
...
return {type: "GET", url: "/step2.php?foo"}
};
// ...
function d(data) { alert("done!"); };
chainajax({type: "GET", url: "/step1.php"},
[a, b, c, d]);
You could also declare the functions "inline" in the call to chainajax, but that might get a little confusing.

Maybe what you can do is write a server-side wrapper function. That way your javascript only does a single asynchronous call to your own web server. Then your web server uses curl (or urllib, etc.) to interact with the remote API.

Update: I've learn a better answer for this if you are using jQuery, see my update under the title: Using jQuery Deffered
Old answer:
You can also use Array.reduceRight (when it's available) to wrap the $.ajax calls and transform a list like: [resource1, resource2] into $.ajax({url:resource1,success: function(...) { $ajax({url: resource2... (a trick that I've learn from Haskell and it's fold/foldRight function).
Here is an example:
var withResources = function(resources, callback) {
var responses = [];
var chainedAjaxCalls = resources.reduceRight(function(previousValue, currentValue, index, array) {
return function() {
$.ajax({url: currentValue, success: function(data) {
responses.push(data);
previousValue();
}})
}
}, function() { callback.apply(null, responses); });
chainedAjaxCalls();
};
Then you can use:
withResources(['/api/resource1', '/api/resource2'], function(response1, response2) {
// called only if the ajax call is successful with resource1 and resource2
});
Using jQuery Deffered
If you are using jQuery, you can take advantage of jQuery Deffered, by using the jQuery.when() function:
jQuery.when($.get('/api/one'), $.get('/api/two'))
.done(function(result1, result2) {
/* one and two is done */
});

Check out this FAQ item on the jQuery site. Specially the callback reference and the complete method.
What you want is data from A to be passed to B and B's data passed to C. So you would do a callback on complete.
I haven't tried this though.

I believe that implementing a state machine will make the code more readable:
var state = -1;
var error = false;
$.ajax({success: function() {
state = 0;
stateMachine(); },
error: function() {
error = true;
stateMachine();
}});
function stateMachine() {
if (error) {
// Error handling
return;
}
if (state == 0) {
state = 1;
// Call stateMachine again in an ajax callback
}
else if (state == 1) {
}
}

I made a method using Promises
// How to setup a chainable queue method
var sequence = Promise.resolve();
function chain(next){
var promise = new Promise(function(resolve){
sequence.then(function(){
next(resolve);
});
});
sequence = promise;
}
// How to use it
chain(function(next){
document.write("<p>start getting config.json</p>");
setTimeout(function(){
document.write("<p>Done fetching config.json</p>");
next();
}, 3000);
});
chain(function(next){
document.write("<p>start getting init.js</p>")
setTimeout(function(){
document.write("<p>starting eval scripting</p>");
next();
}, 3000);
});
chain(function(next){
document.write("<p>Everything is done</p>");
});
Bonus: A ultraligth 138 byte limited A- Promise (that can only resolve - without parameters, and only call the last then-method )
Background:
I made this for node.js at the point where it dose not have promises ATM. I didn't want a complete full blown Promise library that I was dependent on and had to include in my package.json, I needed it to be fast and light and do mostly one thing only. I only needed it for one thing (chaining things like you want to)
function Q(a,b){b=this;a(function(){b.then&&b.then();b.then=i});return b}function i(a){a&&a()}Q.prototype={then:function(a){this.then=a}};
How?
// Start with a resolved object
var promise = new Q(function(a){a()});
// equal to
// var promise = Promise.resolve();
// example usage
new Q(function(resolve){
// do some async stuff that takes time
// setTimeout(resolve, 3000);
}).then(function(){
// its done
// can not return a new Promise
}); // <- can not add more then's (it only register the last one)
and for the chainable queue method
// How to setup a chainable queue method with ultraligth promise
var sequence = new Q(function(a){a()});
function chain(next){
var promise = new Q(function(resolve){
sequence.then(function(){
next(resolve);
});
});
sequence = promise;
}

The complete callback is what you're looking for:
$.ajax({
type: 'post',
url: "www.example.com",
data: {/* Data to be sent to the server. It is converted to a query string, if not already a string. It's appended to the url for GET-requests. */},
success:
function(data) {
/* you can also chain requests here. will be fired if initial request is successful but will be fired before completion. */
},
complete:
function() {
/* For more a more synchronous approach use this callback. Will be fired when first function is completed. */
}
});

Related

How to use promises, or complete an ajax request before the function finishes?

I have the following function to check a users session to see if they're staff or not. Now, I know there are better ways to do this, but I'm trying to make a simple application that's tied with a forum software.
function isStaff(callback) {
$.ajax({
url: url
}).done(function(data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
callback(true);
} else {
callback(false);
}
});
}
Let's say I'm using this function in, like so, when compiling a "post" (Handlebars).
function compilePost(post) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: function() {
isStaff(function(response) {
return response;
});
}
}
var html= template(context);
return html;
}
Problem here, is that the request to check if a user is staff doesn't complete the request until after the function is ran.
I know with Promises is an alternative to async: false, where request is made and the response comes back before the function finishes.
But I have no idea how I can convert this into a promise. I've tried to learn it but I'm stuck at the concept. Can someone explain this to me? Thanks.
First, let's simplify the compilePost function. This function should know how to compile a post in a synchronous manner. Let's change the isStaff fetching to a simple argument.
function compilePost(post, isStaff) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: isStaff
}
var html= template(context);
return html;
}
Now, let's create a new method, with a single purpose - checking if a user is member of the staff:
function checkForStaffMemebership() {
return new Promise(function (resolve, reject) {
$.ajax({
url: url,
success: function (data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
resolve(true);
} else {
resolve(false);
}
}
});
});
}
This function wraps your original ajax call to the server with a promise, whenever the $.ajax call gets a response from the server, the promise will resolve with the answer whether the user is a staff member or not.
Now, we can write another function to orchestrate the process:
function compilePostAsync(post) {
return checkForStaffMemebership()
.then(function (isStaff) {
return compilePost(post, isStaff);
});
}
compilePostAsync finds out whether the user is a staff member or not. Then, it's compiling the post.
Please notice that compilePostAsync returns a promise, and thus if you used to have something like:
element.innerHTML = compilePost(post);
Now, you should change it to something like:
compilePostAsync(post).then(function (compiledPost) {
element.innerHTML = compiledPost;
});
Some notes:
This is only an example, it surely misses some things (proper error handling for example)
The isStaff and checkForStaffMemebership (original and new) do not get any argument, I guess you'd figure out how to pass the userId or any other data you might need
Read about promises, it's a useful tool to have, there is a lot of data about it on the web, for example: MDN.
As per the documentation you dont need to wrap the ajax with a promise which already implements promise. Instead chain the response as explained below.
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the Promise interface, giving them all the properties, methods, and behavior of a Promise (see Deferred object for more information)
You can do something like below by chaining the response:
function isStaff(url, post) {
return $.ajax({
url: url,
dataType:"json"
}).then(function(resp){
//resp = $.parseJSON(resp); /*You dont require this if you have respose as JSON object. Just Specify it in 'dataType'*/
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: resp.is_staff === 1 ? true : false
};
return template(context);
});
}
isStaff(url, post).done(function(template){
/*Your compiled template code is available here*/
}).fail(function(jqXHR, textStatus, errorThrown){
console.log("Error:"+textStatus);
});
Note: Be sure to implement error callbacks also. Because you may never know what
went wrong :)
Simple explanation about promise with $.defer:
For understanding i have created the Fiddle similar to your requirement.
Explanation:
Basically Promise is been introduced to attain synchronous execution of asynchronous JS code.
What do you mean by Async or Asynchronous code?
The code that is executed may return a value at any given point of time which is not immediate. Famous example to support this statement would be jquery ajax.
Why is it required?
Promise implementations helps a developer to implement a synchronous code block which depends on asynchronous code block for response,. like in ajax call when i make a request to server asking for a data string, i need to wait till the server responds back to me with a response data string which my synchronous code uses it to manipulate it , do some logic and update the UI.
Follow this link where the author has explained with detailed examples.
PS: Jquery $.defer implements or wraps promise in quite a different way. Both are used for the same purpose.
let basedataset = {}
let ajaxbase = {};
//setting api Urls
apiinterface();
function apiinterface() {
ajaxbase.createuser = '/api/createuser'
}
//setting up payload for post method
basedataset.email = profile.getEmail()
basedataset.username = profile.getGivenName()
//setting up url for api
ajaxbase.url = ajaxbase.createuser
ajaxbase.payload = basedataset;
//reusable promise based approach
basepostmethod(ajaxbase).then(function(data) {
console.log('common data', data);
}).catch(function(reason) {
console.log('reason for rejection', reason)
});
//modular ajax (Post/GET) snippets
function basepostmethod(ajaxbase) {
return new Promise(function(resolve, reject) {
$.ajax({
url: ajaxbase.url,
method: 'post',
dataType: 'json',
data: ajaxbase.payload,
success: function(data) {
resolve(data);
},
error: function(xhr) {
reject(xhr)
}
});
});
}
A solution using async await in js would be like this:
async function getMyAjaxCall() {
const someVariableName = await ajaxCallFunction();
}
function getMyAjaxCall() {
return $.ajax({
type: 'POST',
url: `someURL`,
headers: {
'Accept':'application/json',
},
success: function(response) {
// in case you need something else done.
}
});
}

Multiple Promise Chains in Single Function

I have some code that will dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request to a server.
The idea is that:
A server provides a "Scenario" for me to generate an AJAX Request.
I generate an AJAX Request based off the Scenario.
I then repeat this process, over and over in a Loop.
I'm doing this with promises here: http://jsfiddle.net/3Lddzp9j/11/
However, I'm trying to edit the code above so I can handle an array of scenarios from the initial AJAX request.
IE:
{
"base": {
"frequency": "5000"
},
"endpoints": [
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/alvarengarichard",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
},
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/dkang",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
}
]
This seems like it would be straight forward, but the issue seems to be in the "waitForTimeout" function.
I'm unable to figure out how to run multiple promise chains. I have an array of promises in the "deferred" variable, but the chain only continues on the first one--despite being in a for loop.
Could anyone provide insight as to why this is? You can see where this is occuring here: http://jsfiddle.net/3Lddzp9j/10/
The main problems are that :
waitForTimeout isn't passing on all the instructions
even if waitForTimeout was fixed, then callApi isn't written to perform multiple ajax calls.
There's a number of other issues with the code.
you really need some data checking (and associated error handling) to ensure that expected components exist in the data.
mapToInstruction is an unnecessary step - you can map straight from data to ajax options - no need for an intermediate data transform.
waitForTimeout can be greatly simplified to a single promise, resolved by a single timeout.
synchronous functions in a promise chain don't need to return a promise - they can return a result or undefined.
Sticking with jQuery all through, you should end up with something like this :
var App = (function ($) {
// Gets the scenario from the API
// sugar for $.ajax with GET as method - NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario2');
};
var checkData = function (data) {
if(!data.endpoints || !data.endpoints.length) {
return $.Deferred().reject('no endpoints').promise();
}
data.base = data.base || {};
data.base.frequency = data.base.frequency || 1000;//default value
};
var waitForTimeout = function(data) {
return $.Deferred(function(dfrd) {
setTimeout(function() {
dfrd.resolve(data.endpoints);
}, data.base.frequency);
}).promise();
};
var callApi = function(endpoints) {
console.log('Calling API with given instructions ...');
return $.when.apply(null, endpoints.map(ep) {
return $.ajax({
type: ep.method,
dataType: ep.type,
url: ep.endpoint
}).then(null, function(jqXHR, textStatus, errorThrown) {
return textStatus;
});
}).then(function() {
//convert arguments to an array of results
return $.map(arguments, function(arg) {
return arg[0];
});
});
};
var handleResults = function(results) {
// results is an array of data values/objects returned by the ajax calls.
console.log("Handling data ...");
...
};
// The 'run' method
var run = function() {
getScenario()
.then(checkData)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(null, function(reason) {
console.error(reason);
})
.then(run);
};
return {
run : run
}
})(jQuery);
App.run();
This will stop on error but could be easily adapted to continue.
I'll try to answer your question using KrisKowal's q since I'm not very proficient with the promises generated by jQuery.
First of all I'm not sure whether you want to solve the array of promises in series or in parallel, in the solution proposed I resolved all of them in parallel :), to solve them in series I'd use Q's reduce
function getScenario() { ... }
function ajaxRequest(instruction) { ... }
function createPromisifiedInstruction(instruction) {
// delay with frequency, not sure why you want to do this :(
return Q.delay(instruction.frequency)
.then(function () {
return this.ajaxRequest(instruction);
});
}
function run() {
getScenario()
.then(function (data) {
var promises = [];
var instruction;
var i;
for (i = 0; i < data.endpoints.length; i += 1) {
instruction = {
method: data.endpoints[i].method,
type: data.endpoints[i].type,
endpoint: data.endpoints[i].endPoint,
frequency: data.base.frequency
};
promises.push(createPromisifiedInstruction(instruction));
}
// alternative Q.allSettled if all the promises don't need to
// be fulfilled (some of them might be rejected)
return Q.all(promises);
})
.then(function (instructionsResults) {
// instructions results is an array with the result of each
// promisified instruction
})
.then(run)
.done();
}
run();
Ok let me explain the solution above:
first of all assume that getScenario gets you the initial json you start with (actually returns a promise which is resolved with the json)
create the structure of each instruction
promisify each instruction, so that each one is actually a promise whose
resolution value will be the promise returned by ajaxRequest
ajaxRequest returns a promise whose resolution value is the result of the request, which also means that createPromisifiedInstruction resolution value will be the resolution value of ajaxRequest
Return a single promise with Q.all, what it actually does is fulfill itself when all the promises it was built with are resolved :), if one of them fails and you actually need to resolve the promise anyways use Q.allSettled
Do whatever you want with the resolution value of all the previous promises, note that instructionResults is an array holding the resolution value of each promise in the order they were declared
Reference: KrisKowal's Q
Try utilizing deferred.notify within setTimeout and Number(settings.frequency) * (1 + key) as setTimeout duration; msg at deferred.notify logged to console at deferred.progress callback , third function argument within .then following timeout
var App = (function ($) {
var getScenario = function () {
console.log("Getting scenario ...");
return $.get("http://demo3858327.mockable.io/scenario2");
};
var mapToInstruction = function (data) {
var res = $.map(data.endpoints, function(settings, key) {
return {
method:settings.method,
type:settings.type,
endpoint:settings.endPoint,
frequency:data.base.frequency
}
});
console.log("Instructions recieved:", res);
return res
};
var waitForTimeout = function(instruction) {
var res = $.when.apply(instruction,
$.map(instruction, function(settings, key) {
return new $.Deferred(function(dfd) {
setTimeout(function() {
dfd.notify("Waiting for "
+ settings.frequency
+ " ms")
.resolve(settings);
}, Number(settings.frequency) * (1 + key));
}).promise()
})
)
.then(function() {
return this
}, function(err) {
console.log("error", err)
}
, function(msg) {
console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n")
});
return res
};
var callApi = function(instruction) {
console.log("Calling API with given instructions ..."
, instruction);
var res = $.when.apply(instruction,
$.map(instruction, function(request, key) {
return request.then(function(settings) {
return $.ajax({
type: settings.method,
dataType: settings.type,
url: settings.endpoint
});
})
})
)
.then(function(data) {
return $.map(arguments, function(response, key) {
return response[0]
})
})
return res
};
var handleResults = function(data) {
console.log("Handling data ..."
, JSON.stringify(data, null, 4));
return data
};
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};
return {
// This will expose only the run method
// but will keep all other functions private
run : run
}
})($);
// ... And start the app
App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
You have a return statement in the loop in your waitForTimeout function. This means that the function is going to return after the first iteration of the loop, and that is where you are going wrong.
You're also using the deferred antipattern and are using promises in places where you don't need them. You don't need to return a promise from a then handler unless there's something to await.
The key is that you need to map each of your instructions to a promise. Array#map is perfect for this. And please use a proper promise library, not jQuery promises (edit but if you absolutely must use jQuery promises...):
var App = (function ($) {
// Gets the scenario from the API
// NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario');
};
// mapToInstructions is basically unnecessary. each instruction does
// not need its own timeout if they're all the same value, and you're not
// reshaping the original values in any significant way
// This wraps the setTimeout into a promise, again
// so we can chain it
var waitForTimeout = function(data) {
var d = $.Deferred();
setTimeout(function () {
d.resolve(data.endpoints);
}, data.base.frequency);
return d.promise();
};
var callApi = function(instruction) {
return $.ajax({
type: instruction.method,
dataType: instruction.type,
url: instruction.endPoint
});
};
// Final step: call the API from the
// provided instructions
var callApis = function(instructions) {
console.log(instructions);
console.log('Calling API with given instructions ...');
return $.when.apply($, instructions.map(callApi));
};
var handleResults = function() {
var data = Array.prototype.slice(arguments);
console.log("Handling data ...");
};
// The 'run' method
var run = function() {
getScenario()
.then(waitForTimeout)
.then(callApis)
.then(handleResults)
.then(run);
};
return {
run : run
}
})($);
App.run();

Making multiple ajax requests synchronously

Let's suppose I have some function called makeRequest(), which makes an AJAX request to a server.
Now let's suppose I am given the amount of times this request should be made, but I can't do them asynchronously but synchronously instead.
For instance, I am given the number 5, and I shall call makeRequest(), when it's done, I shall call it again, and when it's done, I shall call it again... I should end up calling it 5 times.
I'm no expert at JavaScript but I found it easy to handle asynchronous calls by the use of callbacks.
So, my makeRequest() function takes a callback argument that is to be executed when the request has succeeded.
In my previous example, I had to make the request 5 times, so the behaviour should look like:
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
makeRequest(function () {
});
});
});
});
});
How can I design this to behave the same for any argument given to me, be it 6, 12 or even 1?
PS: I have tried many approaches, the most common involving creating while loops that wait until a flag is set by a finished request. All of these approaches makes the browser think the script crashed and prompt the user to stop the script.
Simple, recursively call the ajax request, while keeping track of a count variable:
function makeRequest(count, finalCallback){
someAjaxCall(data, function(){
if(count > 1){
makeRequest(count - 1, finalCallback);
} else {
finalCallback && finalCallback();
}
});
}
finalCallback is a optional callback (function) that will be executed when all the requests are completed.
You can do it this way,
var i = 5; // number of calls to you function calling ajax
recurs(i); // call it initially
function recurs(count) {
makeRequest(function() {
count--; // decrement count
if (count > 1) {
recurs(count) // call function agian
}
});
}
Here I have written multiple Ajax calls using promises. This function will run synchronously. You can get the current position of response which is executed from Ajax.
var ajaxs = {
i : 0,
callback : null,
param : null,
exec_fun : function (i) {
let data_send = this.param[i];
let url = this.url;
this.promise = new Promise(function (res,rej) {
$.ajax({
url: url,
method: 'POST',
data: data_send,
dataType: 'json',
success: function(resvalidate){
res(resvalidate);
}
});
});
this.promise.then(function (resvalidate) {
let resp = resvalidate,
param = ajaxs.param,
pos = ajaxs.i,
callback_fun = ajaxs.callback_fun;
callback_fun(resp,ajaxs.i);
ajaxs.i++;
if( ajaxs.param[ajaxs.i] != undefined){
ajaxs.exec_fun(ajaxs.i);
}
});
},
each : function (url,data,inc_callback) {
this.callback_fun = inc_callback;
this.param = data;
this.url = url;
this.exec_fun(ajaxs.i);
}
};
let url = "http://localhost/dev/test_ajax.php";
let data_param = [{data : 3},{data : 1},{data : 2}];
ajaxs.each(url,data_param, function (resp,i) {
console.log(resp,i);
});

Sequencing ajax requests

I find I sometimes need to iterate some collection and make an ajax call for each element. I want each call to return before moving to the next element so that I don't blast the server with requests - which often leads to other issues. And I don't want to set async to false and freeze the browser.
Usually this involves setting up some kind of iterator context that i step thru upon each success callback. I think there must be a cleaner simpler way?
Does anyone have a clever design pattern for how to neatly work thru a collection making ajax calls for each item?
jQuery 1.5+
I developed an $.ajaxQueue() plugin that uses the $.Deferred, .queue(), and $.ajax() to also pass back a promise that is resolved when the request completes.
/*
* 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
If you're using jQuery 1.4, you can utilize the animation queue on an empty object to create your own "queue" for your ajax requests for the elements.
You can even factor this into your own $.ajax() replacement. This plugin $.ajaxQueue() uses the standard 'fx' queue for jQuery, which will auto-start the first added element if the queue isn't already running.
(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);
Example Usage
So, we have a <ul id="items"> which has some <li> that we want to copy (using ajax!) to the <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 }));
}
});
});
jsfiddle demonstration - 1.4 version
A quick and small solution using deferred promises. Although this uses jQuery's $.Deferred, any other should do.
var Queue = function () {
var previous = new $.Deferred().resolve();
return function (fn, fail) {
return previous = previous.then(fn, fail || fn);
};
};
Usage, call to create new queues:
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');
});
See the example with a side-by-side comparison of asynchronous requests.
This works by creating a function that will automatically chain promises together. The synchronous nature comes from the fact that we are wrapping $.get calls in function and pushing them into a queue. The execution of these functions are deferred and will only be called when it gets to the front of the queue.
A requirement for the code is that each of the functions you give must return a promise. This returned promise is then chained onto the latest promise in the queue. When you call the queue(...) function it chains onto the last promise, hence the previous = previous.then(...).
You can wrap all that complexity into a function to make a simple call that looks like this:
loadSequantially(['/a', '/a/b', 'a/b/c'], function() {alert('all loaded')});
Below is a rough sketch (working example, except the ajax call). This can be modified to use a queue-like structure instead of an array
// 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
}
};
Ideally, a coroutine with multiple entry points so every callback from server can call the same coroutine will be neat. Damn, this is about to be implemented in Javascript 1.7.
Let me try using closure...
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);
Yeah, while the other answers will work, they are lots of code and messy looking. Frame.js was designed to elegantly address this situation. https://github.com/bishopZ/Frame.js
For instance, this will cause most browsers to hang:
for(var i=0; i<1000; i++){
$.ajax('myserver.api', { data:i, type:'post' });
}
While this will not:
for(var i=0; i<1000; i++){
Frame(function(callback){
$.ajax('myserver.api', { data:i, type:'post', complete:callback });
});
}
Frame.start();
Also, using Frame allows you to waterfall the response objects and deal with them all after the entire series of AJAX request have completed (if you want to):
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()
I am posting this answer thinking that it might help other persons in future, looking for some simple solutions in the same scenario.
This is now possible also using the native promise support introduced in ES6. You can wrap the ajax call in a promise and return it to the handler of the element.
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
});
});
}
Now call the function recursively, from where you have the collection of the elements.
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;
}
}
I use http://developer.yahoo.com/yui/3/io/#queue to get that functionality.
The only solutions I can come up with is, as you say, maintaining a list of pending calls / callbacks. Or nesting the next call in the previous callback, but that feels a bit messy.
You can achieve the same thing using 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);
}
http://plnkr.co/edit/meHQHU48zLTZZHMCtIHm?p=preview
I would suggest a bit more sophisticated approach which is reusable for different cases.
I am using it for example when I need to slow down a call sequence when the user is typing in text editor.
But I am sure it should also work when iterating through the collection. In this case it can queue requests and can send a single AJAX call instead of 12.
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++;
}
}
There's a very simple way to achieve this by adding async: false as a property to the ajax call. This will make sure the ajax call is complete before parsing the rest of the code. I have used this successfully in loops many times.
Eg.
$.ajax({
url: "",
type: "GET",
async: false
...

Parallel asynchronous Ajax requests using jQuery

I'd like to update a page based upon the results of multiple ajax/json requests. Using jQuery, I can "chain" the callbacks, like this very simple stripped down example:
$.getJSON("/values/1", function(data) {
// data = {value: 1}
var value_1 = data.value;
$.getJSON("/values/2", function(data) {
// data = {value: 42}
var value_2 = data.value;
var sum = value_1 + value_2;
$('#mynode').html(sum);
});
});
However, this results in the requests being made serially. I'd much rather a way to make the requests in parallel, and perform the page update after all are complete. Is there any way to do this?
jQuery $.when() and $.done() are exactly what you need:
$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
.then(myFunc, myFailure);
Try this solution, which can support any specific number of parallel queries:
var done = 4; // number of total requests
var sum = 0;
/* Normal loops don't create a new scope */
$([1,2,3,4,5]).each(function() {
var number = this;
$.getJSON("/values/" + number, function(data) {
sum += data.value;
done -= 1;
if(done == 0) $("#mynode").html(sum);
});
});
Run multiple AJAX requests in parallel
When working with APIs, you sometimes need to issue multiple AJAX requests to different endpoints. Instead of waiting for one request to complete before issuing the next, you can speed things up with jQuery by requesting the data in parallel, by using jQuery's $.when() function:
JS
$.when($.get('1.json'), $.get('2.json')).then(function(r1, r2){
console.log(r1[0].message + " " + r2[0].message);
});
The callback function is executed when both of these GET requests finish successfully. $.when() takes the promises returned by two $.get() calls, and constructs a new promise object. The r1 and r2 arguments of the callback are arrays, whose first elements contain the server responses.
Here's my attempt at directly addressing your question
Basically, you just build up and AJAX call stack, execute them all, and a provided function is called upon completion of all the events - the provided argument being an array of the results from all the supplied ajax requests.
Clearly this is early code - you could get more elaborate with this in terms of the flexibility.
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
var ParallelAjaxExecuter = function( onComplete )
{
this.requests = [];
this.results = [];
this.onComplete = onComplete;
}
ParallelAjaxExecuter.prototype.addRequest = function( method, url, data, format )
{
this.requests.push( {
"method" : method
, "url" : url
, "data" : data
, "format" : format
, "completed" : false
} )
}
ParallelAjaxExecuter.prototype.dispatchAll = function()
{
var self = this;
$.each( self.requests, function( i, request )
{
request.method( request.url, request.data, function( r )
{
return function( data )
{
console.log
r.completed = true;
self.results.push( data );
self.checkAndComplete();
}
}( request ) )
} )
}
ParallelAjaxExecuter.prototype.allRequestsCompleted = function()
{
var i = 0;
while ( request = this.requests[i++] )
{
if ( request.completed === false )
{
return false;
}
}
return true;
},
ParallelAjaxExecuter.prototype.checkAndComplete = function()
{
if ( this.allRequestsCompleted() )
{
this.onComplete( this.results );
}
}
var pe = new ParallelAjaxExecuter( function( results )
{
alert( eval( results.join( '+' ) ) );
} );
pe.addRequest( $.get, 'test.php', {n:1}, 'text' );
pe.addRequest( $.get, 'test.php', {n:2}, 'text' );
pe.addRequest( $.get, 'test.php', {n:3}, 'text' );
pe.addRequest( $.get, 'test.php', {n:4}, 'text' );
pe.dispatchAll();
</script>
here's test.php
<?php
echo pow( $_GET['n'], 2 );
?>
Update: Per the answer given by Yair Leviel, this answer is obsolete. Use a promise library, like jQuery.when() or Q.js.
I created a general purpose solution as a jQuery extension. Could use some fine tuning to make it more general, but it suited my needs. The advantage of this technique over the others in this posting as of the time of this writing was that any type of asynchronous processing with a callback can be used.
Note: I'd use Rx extensions for JavaScript instead of this if I thought my client would be okay with taking a dependency on yet-another-third-party-library :)
// jQuery extension for running multiple async methods in parallel
// and getting a callback with all results when all of them have completed.
//
// Each worker is a function that takes a callback as its only argument, and
// fires up an async process that calls this callback with its result.
//
// Example:
// $.parallel(
// function (callback) { $.get("form.htm", {}, callback, "html"); },
// function (callback) { $.post("data.aspx", {}, callback, "json"); },
// function (formHtml, dataJson) {
// // Handle success; each argument to this function is
// // the result of correlating ajax call above.
// }
// );
(function ($) {
$.parallel = function (anyNumberOfWorkers, allDoneCallback) {
var workers = [];
var workersCompleteCallback = null;
// To support any number of workers, use "arguments" variable to
// access function arguments rather than the names above.
var lastArgIndex = arguments.length - 1;
$.each(arguments, function (index) {
if (index == lastArgIndex) {
workersCompleteCallback = this;
} else {
workers.push({ fn: this, done: false, result: null });
}
});
// Short circuit this edge case
if (workers.length == 0) {
workersCompleteCallback();
return;
}
// Fire off each worker process, asking it to report back to onWorkerDone.
$.each(workers, function (workerIndex) {
var worker = this;
var callback = function () { onWorkerDone(worker, arguments); };
worker.fn(callback);
});
// Store results and update status as each item completes.
// The [0] on workerResultS below assumes the client only needs the first parameter
// passed into the return callback. This simplifies the handling in allDoneCallback,
// but may need to be removed if you need access to all parameters of the result.
// For example, $.post calls back with success(data, textStatus, XMLHttpRequest). If
// you need textStatus or XMLHttpRequest then pull off the [0] below.
function onWorkerDone(worker, workerResult) {
worker.done = true;
worker.result = workerResult[0]; // this is the [0] ref'd above.
var allResults = [];
for (var i = 0; i < workers.length; i++) {
if (!workers[i].done) return;
else allResults.push(workers[i].result);
}
workersCompleteCallback.apply(this, allResults);
}
};
})(jQuery);
UPDATE And another two years later, this looks insane because the accepted answer has changed to something much better! (Though still not as good as Yair Leviel's answer using jQuery's when)
18 months later, I just hit something similar. I have a refresh button, and I want the old content to fadeOut and then the new content to fadeIn. But I also need to get the new content. The fadeOut and the get are asynchronous, but it would be a waste of time to run them serially.
What I do is really the same as the accepted answer, except in the form of a reusable function. Its primary virtue is that it is much shorter than the other suggestions here.
var parallel = function(actions, finished) {
finishedCount = 0;
var results = [];
$.each(actions, function(i, action) {
action(function(result) {
results[i] = result;
finishedCount++;
if (finishedCount == actions.length) {
finished(results);
}
});
});
};
You pass it an array of functions to run in parallel. Each function should accept another function to which it passes its result (if any). parallel will supply that function.
You also pass it a function to be called when all the operations have completed. This will receive an array with all the results in. So my example was:
refreshButton.click(function() {
parallel([
function(f) {
contentDiv.fadeOut(f);
},
function(f) {
portlet.content(f);
},
],
function(results) {
contentDiv.children().remove();
contentDiv.append(results[1]);
contentDiv.fadeIn();
});
});
So when my refresh button is clicked, I launch jQuery's fadeOut effect and also my own portlet.content function (which does an async get, builds a new bit of content and passes it on), and then when both are complete I remove the old content, append the result of the second function (which is in results[1]) and fadeIn the new content.
As fadeOut doesn't pass anything to its completion function, results[0] presumably contains undefined, so I ignore it. But if you had three operations with useful results, they would each slot into the results array, in the same order you passed the functions.
you could do something like this
var allData = []
$.getJSON("/values/1", function(data) {
allData.push(data);
if(data.length == 2){
processData(allData) // where process data processes all the data
}
});
$.getJSON("/values/2", function(data) {
allData.push(data);
if(data.length == 2){
processData(allData) // where process data processes all the data
}
});
var processData = function(data){
var sum = data[0] + data[1]
$('#mynode').html(sum);
}
Here's an implementation using mbostock/queue:
queue()
.defer(function(callback) {
$.post('/echo/json/', {json: JSON.stringify({value: 1}), delay: 1}, function(data) {
callback(null, data.value);
});
})
.defer(function(callback) {
$.post('/echo/json/', {json: JSON.stringify({value: 3}), delay: 2}, function(data) {
callback(null, data.value);
});
})
.awaitAll(function(err, results) {
var result = results.reduce(function(acc, value) {
return acc + value;
}, 0);
console.log(result);
});
The associated fiddle: http://jsfiddle.net/MdbW2/
With the following extension of JQuery (to can be written as a standalone function you can do this:
$.whenAll({
val1: $.getJSON('/values/1'),
val2: $.getJSON('/values/2')
})
.done(function (results) {
var sum = results.val1.value + results.val2.value;
$('#mynode').html(sum);
});
The JQuery (1.x) extension whenAll():
$.whenAll = function (deferreds) {
function isPromise(fn) {
return fn && typeof fn.then === 'function' &&
String($.Deferred().then) === String(fn.then);
}
var d = $.Deferred(),
keys = Object.keys(deferreds),
args = keys.map(function (k) {
return $.Deferred(function (d) {
var fn = deferreds[k];
(isPromise(fn) ? fn : $.Deferred(fn))
.done(d.resolve)
.fail(function (err) { d.reject(err, k); })
;
});
});
$.when.apply(this, args)
.done(function () {
var resObj = {},
resArgs = Array.prototype.slice.call(arguments);
resArgs.forEach(function (v, i) { resObj[keys[i]] = v; });
d.resolve(resObj);
})
.fail(d.reject);
return d;
};
See jsbin example:
http://jsbin.com/nuxuciwabu/edit?js,console
The most professional solution for me would be by using async.js and Array.reduce like so:
async.map([1, 2, 3, 4, 5], function (number, callback) {
$.getJSON("/values/" + number, function (data) {
callback(null, data.value);
});
}, function (err, results) {
$("#mynode").html(results.reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
}));
});
If the result of one request depends on the other, you can't make them parallel.
Building on Yair's answer.
You can define the ajax promises dynamically.
var start = 1; // starting value
var len = 2; // no. of requests
var promises = (new Array(len)).fill().map(function() {
return $.ajax("/values/" + i++);
});
$.when.apply($, promises)
.then(myFunc, myFailure);
Suppose you have an array of file name.
var templateNameArray=["test.html","test2.html","test3.html"];
htmlTemplatesLoadStateMap={};
var deffereds=[];
for (var i = 0; i < templateNameArray.length; i++)
{
if (!htmlTemplatesLoadStateMap[templateNameArray[i]])
{
deferreds.push($.get("./Content/templates/" +templateNameArray[i],
function (response, status, xhr) {
if (status == "error") { }
else {
$("body").append(response);
}
}));
htmlTemplatesLoadStateMap[templateNameArray[i]] = true;
}
}
$.when.all(deferreds).always(function(resultsArray) { yourfunctionTobeExecuted(yourPayload);
});
I needed multiple, parallel ajax calls, and the jquery $.when syntax wasn't amenable to the full $.ajax format I am used to working with. So I just created a setInterval timer to periodically check when each of the ajax calls had returned. Once they were all returned, I could proceed from there.
I read there may be browser limitations as to how many simultaneous ajax calls you can have going at once (2?), but .$ajax is inherently asynchronous, so making the ajax calls one-by-one would result in parallel execution (within the browser's possible limitation).

Categories

Resources