Synchronous request in Windows Azure? - javascript

So in my server code, variable invites is undefined outside of the success function.
function getInvites(id){
var InvitesTable = tables.getTable("Invites").where({"PlanID": id}).select("UserID","Attending");
var invites;
InvitesTable.read({ success: function(resultss) {
invites = resultss;
console.log(invites); //works here
}});
console.log(invites); //undefined here
}
From similar questions, I realize its because of it being asynchronous. So the success function call is run after the console.log(invites); //undefined here call.
My question is how do I stop that in Windows Azure?
Added code
function read(query, user, request) {
request.execute({
success: function(results) {
results.forEach(function(r) {
getInvites(r.id, function(invites) {
r.invites = invites;
});
});
request.respond();
}
});
}
function getInvites(id, cb){
var InvitesTable = tables.getTable("Invites").where({"PlanID": id}).select("UserID","Attending");
InvitesTable.read({ success: function(results) {
if (cb) cb(results);
}});
}

You don't "stop that," you design your application around the async nature of whatever environment you're using.
I assume you're trying to do something like this:
function getInvites(id){
var InvitesTable = tables.getTable("Invites").where({"PlanID": id}).select("UserID","Attending");
var invites;
InvitesTable.read({ success: function(resultss) {
invites = resultss;
}});
return invites;
}
// later...
var invites = getInvites(someId);
//do something with `invites`
This obviously won't work, since you return the value of invites before the async call completes.
Instead, you write your app in async style:
function getInvites(id, cb){
var InvitesTable = tables.getTable("Invites").where({"PlanID": id}).select("UserID","Attending");
InvitesTable.read({ success: function(resultss) {
if (cb) cb(resultss);
}});
}
// later...
getInvites(someId, function(invites) {
//do something with `invites`
});
This leaves out error handling code for the sake of simplicity, so you'd have to add that as well.
After seeing your full code, it looks like you have a simple problem of managing many parallel asynchronous operations. Consider what happens: your loop runs, iterating over an array of n objects. For each, you call getInvites, which begins a database request and returns.
This means your loop runs very quickly, but now you have n outstanding database requests that you must wait on before you can call request.respond().
An extremely basic solution would be to do something like count the number of times your getInvites callback is called, and then finally complete the request when that number reaches n.
However, it is time-consuming and mistake-prone to manage this bookkeeping manually every time you make async requests. This is a situation where flow control libraries are extremely useful. I will use jQuery's Deferred in this example, since it may already be familiar to you (even if you don't know you've actually used it before — if you've ever used jQuery's XHR API, you've used Deferreds).
Given that you're in a server environment, you obviously don't have jQuery; however, there are people who have extracted only the code necessary for Deferred for you.
Once we have Deferreds for every pending request, we can use when to register a callback that gets called only after all pending Deferreds complete.
function read(query, user, request) {
request.execute({
success: function(results) {
var dfds = [];
for (var i = 0; i < results.length; i++) {
dfds.push(getInvites(results[i].id)); // Makes an array of Deferreds representing
// each of our pending requests.
}
Deferred.when.apply(Deferred, dfds) // see details below
.done(function() {
for (var i = 0; i < results.length; i++) {
results[i].invites = arguments[i]; // Copy each set of invites to each result
}
request.respond(); // We're done!
})
.fail(function() {
// Handle errors here
});
}
});
}
function getInvites(id){
var dfd = new Deferred(); // Create a new Deferred, which automatically starts in the 'pending' state
var InvitesTable = tables.getTable("Invites").where({"PlanID": id}).select("UserID","Attending");
InvitesTable.read({ success: function(results) {
dfd.resolve(results); // When we get data back, we 'resolve' the Deferred --
// in other words, say its operation is done,
// and pass along the operation's results.
},
error: function(err) { // TODO: Not sure if this is how the API you're using handles errors
dfd.reject(err); // Marks the Deferred as failed.
}});
return dfd.promise(); // We (synchronously) return the Promise. The caller can attach event handlers
// to the Promise, which are invoked when we eventually resolve or reject the Deferred.
}
Notes:
jQuery.when (or in this server-side case, Deferred.when) normally expects you to pass a fixed number of Deferreds as arguments:
$.when(dfd1, dfd2).done(function(result1, result2) { ... });
However, we have a variable number of Deferreds, so we must apply an array of Deferreds to when and then in the done handler, access each result via the implicit arguments object.
Array.forEach(...) is slow. In most cases, it is better to use a regular for loop.

I've stumbled on same need for synchronous DB access, so I wrote small module called query-synchronizer.
Idea was to count how many times query was started and ended. If all started count was equal to ended count, other part of code would be executed. Your code would look like this:
var synchronizer = require('query-synchronizer');
function read(query, user, request) {
request.execute({
success: function(results) {
results.forEach(function(r) {
var InvitesTable = tables.getTable("Invites").where({"PlanID": r.id}).select("UserID","Attending");
synchronizer.read(InvitesTable, function(results){
r.invites = invites;
});
});
synchronizer.done(function(){
request.respond();
});
}
});
}

Related

Javascript esriRequest (dojo) in a function async issue

I am facing the following synchronization issue. I wouldn't be surprised if it has a simple solution/workaround. The BuildMenu() function is called from another block of code and it calls the CreateMenuData() which makes a request to a service which return some data. The problem is that since it is an async call to the service when the data variable is being used it is undefined. I have provided the js log that also shows my point.
BuildMenu: function () {
console.log("before call");
var data=this.CreateMenuData();
console.log("after call");
//Doing more stuff with data that fail.
}
CreateMenuData: function () {
console.log("func starts");
data = [];
dojo.forEach(config.layerlist, function (collection, colindex) {
var layersRequest = esriRequest({
url: collection.url,
handleAs: "json",
});
layersRequest.then(
function (response) {
dojo.forEach(response.records, function (value, key) {
console.log(key);
data.push(key);
});
}, function (error) {
});
});
console.log("func ends");
return data;
}
Console log writes:
before call
func starts
func ends
after call
0
1
2
3
4
FYI: using anything "dojo." is deprecated. Make sure you are pulling all the modules you need in "require".
Ken has pointed you the right direction, go through the link and get familiarized with the asynchronous requests.
However, I'd like to point out that you are not handling only one async request, but potentionally there might be more of them of which you are trying to fill the "data" with. To make sure you handle the results only when all of the requests are finished, you should use "dojo/promise/all".
CreateMenuData: function (callback) {
console.log("func starts");
requests = [];
data = [];
var scope = this;
require(["dojo/_base/lang", "dojo/base/array", "dojo/promise/all"], function(lang, array, all){
array.forEach(config.layerlist, function (collection, colindex) {
var promise = esriRequest({
url: collection.url,
handleAs: "json",
});
requests.push(promise);
});
// Now use the dojo/promise/all object
all(requests).then(function(responses){
// Check for all the responses and add whatever you need to the data object.
...
// once it's all done, apply the callback. watch the scope!
if (typeof callback == "function")
callback.apply(scope, data);
});
});
}
so now you have that method ready, call it
BuildMenu: function () {
console.log("before call");
var dataCallback = function(data){
// do whatever you need to do with the data or call other function that handles them.
}
this.CreateMenuData(dataCallback);
}

In parse object.save(); doesnt return anything why?

Here is my code, it loops through forEach and prints out '1' but never returns from object.save() & never prints out 2, 3 or anything else. I have tried a bunch of other ways but none seems to work.
Note: response.succes(or error) is not being called anywhere, the code is definitely waiting for object.save() to be completed.
var promise = new Parse.Promise();
var query = new Parse.Query("SomeClass");
query.find().then(function(results) {
var promises = [];
results.forEach(function(object) {
object.set("SomeColumnName", true);
console.log('1');
promises.push(object.save(null, {
success: function(result) {
alert('2');
return ;
},
error: function(result, error) {
alert('3');
return ;
}
}));
});
Parse.Promise.when(promises).then(function() {
console.log('inside resolve');
promise.resolve();
}, function() {
console.log('inside reject');
promise.reject();
});
});
return promise;
You're on the right track, but you should take advantage of the fact that most of the sdk functions create and return promises for you. With those, you can substantially simplify the code:
// very handy utility library that provides _.each among many other things
// www.underscorejs.org
var _ = require('underscore');
// answer a promise to modify all instances of SomeClass
function changeSomeClass() {
var query = new Parse.Query("SomeClass");
// if there are more than 100 rows, set query.limit up to 1k
return query.find().then(function(results) { // find returns a promise
_.each(results, function(result) {
result.set("SomeColumnName", true);
});
return Parse.Object.saveAll(results); // and saveAll returns a promise
});
}
Wrap it in a cloud function and call success/error like this:
Parse.Cloud.define("changeSomeClass", function(request, response) {
changeSomeClass().then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
});
You can only have one Parse request happening at a time for each object. If multiple requests are sent, all but the first are ignored. You're probably trying to do this while other threads are making Parse requests for those objects. I know that if you save an object, it also saves it's child objects, so you could be hitting a problem with that. Make sure you do as much as you can in background threads with completion blocks, or use saveEventually / fetchEventually where possible.

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
...

Best way to add a 'callback' after a series of asynchronous XHR calls

I stumbled on a piece of Ajax code that is not 100% safe since it's mixing asynchronous/synchronous type of code... so basically in the code below I have a jQuery.each in which it grabs information on the elements and launch an Ajax get request for each:
$(search).each(function() {
$.ajax({
url: 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
success: function(o){
//Update UI
},
error: function(o){
//Update UI
}
});
});
//code to do after saving...
So obviously the 'code to do after saving...' often gets executed before all the requests are completed. In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback but assuming this is not possible, I changed the code to something like this to make sure all requests came back before continuing which I'm still not in love with:
var recs = [];
$(search).each(function() {
recs[recs.length] = 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
});
var counter = 0;
function saveRecords(){
$.ajax({
url: recs[counter],
success: function(o){
//Update progress
if (counter<recs.length){
counter++;
saveRecords();
}else{
doneSavingRecords();
}
},
error: function(o){
//Update progress
doneSavingRecords(o.status);
}
});
}
function doneSavingRecords(text){
//code to do after saving...
}
if (recs.length>0){
saveRecords(); //will recursively callback itself until a failed request or until all records were saved
}else{
doneSavingRecords();
}
So I'm looking for the 'best' way to add a bit of synchronous functionality to a series of asynchronous calls ?
Thanks!!
Better Answer:
function saveRecords(callback, errorCallback){
$('<div></div>').ajaxStop(function(){
$(this).remove(); // Keep future AJAX events from effecting this
callback();
}).ajaxError(function(e, xhr, options, err){
errorCallback(e, xhr, options, err);
});
$(search).each(function() {
$.get('save.x3', { id: $(this).attr("id"), value: $(this).data("value") });
});
}
Which would be used like this:
saveRecords(function(){
// Complete will fire after all requests have completed with a success or error
}, function(e, xhr, options, err){
// Error will fire for every error
});
Original Answer: This is good if they need to be in a certain order or you have other regular AJAX events on the page that would affect the use of ajaxStop, but this will be slower:
function saveRecords(callback){
var recs = $(search).map(function(i, obj) {
return { id: $(obj).attr("id"), value: $(obj).data("value") };
});
var save = function(){
if(!recs.length) return callback();
$.ajax({
url: 'save.x3',
data: recs.shift(), // shift removes/returns the first item in an array
success: function(o){
save();
},
error: function(o){
//Update progress
callback(o.status);
}
});
}
save();
}
Then you can call it like this:
saveRecords(function(error){
// This function will run on error or after all
// commands have run
});
If I understand what you're asking, I think you could use $.ajaxStop() for this purpose.
This is easily solved by calling the same function to check that all AJAX calls are complete. You just need a simple queue shared between functions, and a quick check (no loops, timers, promises, etc).
//a list of URLs for which we'll make async requests
var queue = ['/something.json', '/another.json'];
//will contain our return data so we can work with it
//in our final unified callback ('displayAll' in this example)
var data = [];
//work through the queue, dispatch requests, check if complete
function processQueue( queue ){
for(var i = 0; i < queue.length; i++){
$.getJSON( queue[i], function( returnData ) {
data.push(returnData);
//reduce the length of queue by 1
//don't care what URL is discarded, only that length is -1
queue.pop();
checkIfLast(displayAll(data));
}).fail(function() {
throw new Error("Unable to fetch resource: " + queue[i]);
});
}
}
//see if this is last successful AJAX (when queue == 0 it is last)
//if this is the last success, run the callback
//otherwise don't do anything
function checkIfLast(callback){
if(queue.length == 0){
callback();
}
}
//when all the things are done
function displayAll(things){
console.log(things); //show all data after final ajax request has completed.
}
//begin
processQueue();
Edit: I should add that I specifically aimed for an arbitrary number of items in the queue. You can simply add another URL and this will work just the same.
>> In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback
You'll need to think about this in terms of events.
Closure's net.BulkLoader (or a similar approach) will do it for you:
http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/class_goog_net_BulkLoader.html
http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/closure_goog_net_bulkloader.js.source.html
See:
goog.net.BulkLoader.prototype.handleSuccess_ (for individual calls)
&
goog.net.BulkLoader.prototype.finishLoad_ (for completion of all calls)

How to chain ajax requests?

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. */
}
});

Categories

Resources