How to resolve race condition when using $.when - javascript

I am trying to use jQuerys $.when() to load a bunch of localization resources before initializing the control on the client side:
var fooControl = (function($, kendo, _) {
var
initResources = function() {
return $.when(
window.clientResources.getAll("Messages").done(function(d) {
resources["Messages"] = d;
}),
window.clientResources.getAll("Cost").done(function(d) {
resources["Cost"] = d;
})
);
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init(options));
});
What I want to achieve is, that initResources waits until the resources are loaded up and assigned to their variables. They are either loaded up from an API endpoint or localStorage, if the data is cached.
What actually happens is, that I am receiving an error Cannot read property 'CostType' of undefined, which indicates, that the Cost resources haven't been fully loaded yet.
So I suspect, that the calls to window.clientResources.getAll() are being resolved properly, but not the following .done method and this then results in a race condition, the resources are losing.
How can I make sure, that the whole call stack, including the assignment of the resources variable has been resolved and only then the following init function is called?

You are invoking the init immediately and passing its return value as success callback handler, A simple solution would be to use a anonymous method
fooControl.initResources().then(function(){
fooControl.init(options);
});

You could use $.Deferred() for this, then resolve that only when the resources have been loaded.
Also as Satpal noted, then needs to be passed either an anonymous function or a function reference.
var fooControl = (function($, kendo, _) {
var initResources = function() {
var deferred = $.Deferred();
$.when(
window.clientResources.getAll("Messages"),
window.clientResources.getAll("Cost")
).done(function(msgData, costData) {
resources["Messages"] = msgData;
resources["Cost"] = costData;
deferred.resolve();
});
return deferred.promise();
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init.bind(this, options));
});

Related

chaining jquery .when().then() in a loop with fixed end of chain call

The closest answer I could find was this https://stackoverflow.com/a/17216555/2834734
The most common use for .then is chaining ajax requests:
$.ajax({...}).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); });
this can easily be done in a loop
However it's the looping procedure I'm having difficulty with plus I have some unusual circumstances.
A brief explanation is, I have an array of requests that I need to loop through, some will invoke an ajax load and others will not. I need them to run consecutively but also run a specific function call at then end.
Here is a simple(I hope) sample of my situation:
// Here is my flow of logic
var thingsToDo = new tasks(); // Initiate the constructor, explained below
// Loop through the requests array and process them consecutively
for (var i in thingsToDo.requests) {
$.when(thingsToDo.jqxhr).then(function() {
thingsToDo.requests[i].fn();
})
}
// Run my final function at the end of the chain.
$.when(thingsToDo.jqxhr).then(function() {
runOnceAllComplete();
});
This is the constructor class the above is based on.
// Constructor
function tasks() {
_tasks_ = this; // automatic global var
this.jqxhr = undefined; // Var to monitor ajax calls
this.requests = [ // list of tasks to run.
{
url: 'file1.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
}, {
url: 'file2.php',
fn: function() {
_tasks_.load(this.url);
console.log('file2 loaded');
}
}, {
noUrl: true, // Note there is no file to load here
fn: function() {
console.log('no file here to load, but process something else');
$('body').css("background-color", "blue");
}
}, {
url: 'file3.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
},
];
this.load = function(file) { // This runs the ajax call and resets this.jqxhr
this.jqxhr = $.get(file);
}
}
function runOnceAllComplete() {
alert('hooray!, we finished');
}
The tricky part I have is the requests are created dynamically so there can be 1-n many requests to perform, which is why I chose to loop, and they must be performed in that order.
As mentioned some requests will invoke an ajax call and others may not, this doesn't seem to break $.when().then(), but the problem is the loop continues before the promise is resolved and my final function happens before the final request.
Still trying to get my head around promises, the first time I've used them.
Try including return statement at fn , this.load ; adding .promise() chained to $("body") at fn to return a jQuery promise object ; using Function.prototype.apply() , $.map() at $.when()
fn: function() {
// added `return`
return _tasks_.load(this.url);
}
this.load = function(file) {
this.jqxhr = $.get(file);
// added `return`
return this.jqxhr
}
fn: function() {
console.log('no file here to load, but process something else');
// added `return` , `.promise()`
return $('body').css("background-color", "blue").promise();
}
$.when.apply($, $.map(thingsToDo.requests, function(task) {
return task.fn()
})).then(runOnceAllComplete)
See also Pass in an array of Deferreds to $.when() , What does $.when.apply($, someArray) do?
however I'm encountering a problem, using the .map() it doesn't wait
for each request to complete before processing the next one. I need
each one to complete before moving to the next.
Try using .queue() , which will calls functions in queue sequentially, and only when next is called at current function
$(thingsToDo).queue("tasks", $.map(thingsToDo.requests, function(task) {
return function(next) {
// call next function in `"tasks"` queue
// when current function completes using `.then(next)`
return task.fn().then(next)
}
})).dequeue("tasks").promise("tasks").then(runOnceAllComplete)
See .queue() , .promise() , Execute function queue in javascript

Getting correct data when using multiple deferred ajax calls

I have a function that uses two ajax calls in order to get the proper information:
var getUsers = function() {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(foo) {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(bar) {
return foo['age'] = bar.type;
});
});
}
And an outside function that calls the current function and only continues when the calls are finished.
getUsers().then(function(result) {
// ...
});
Now the weird thing is that if I display the result, the 'age' will show up in the console, but if I try to access it using result['age'], it will return undefined.
Is there a proper way of handling multiple deferred calls?
Code
http://codepen.io/norbiu/pen/bNRQxL
Edit Instead of using a separate deferred, you can chain the ones returned from getJSON() like this
var getUsers = function() {
var foo;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.then(function(data) {
foo = data;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
}).then(function(bar) {
foo['age'] = bar.type;
return foo;
});
}
Note: you need to save the return value from the first call or it won't be accessible to the second.
Original code for posterity
You can use a jQuery Deferred object and return that instead
var getUsers = function() {
var dfd = $.Deferred();
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(foo) {
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(bar) {
foo['age'] = bar.type;
dfd.resolve(foo);
}).fail(function(e) {
dfd.reject(e);
})
}).fail(function(e) {
dfd.reject(e);
});
return dfd.promise();
}
http://codepen.io/anon/pen/pvwqZo
The deferred object won't resolve until both requests succeed (and will fail if any of them fail).

Difficulty wrapping a javascript behavior and holding it for later

I am getting into programming with javascript and using Promises, right now using Q.js. I have finally gotten to a point where I understand what I am doing, but am having a difficult time with a specific behavior.
I have one situation where I have reasonably similar code repeated several times. It basically goes like this ...
{
// start
var deferred = Q.defer();
// do something {
deferred.resolve();
}
return deferred.promise;
}
Okay, that's all fine and good, but repeating all of this every time was getting annoying, so I attempted to wrap it up in something. This is just an example, it is not the entire javascript file, since most of the other parts are not relevant.
{
var list = [];
queue = function(f) {
var deferred = Q.defer();
list.push(f(deferred));
return deferred.promise;
}
{
queue(function(deferred){
// do some work
// we want the deferred here so we can resolve it at the correct time
deferred.resolve();
});
}
}
The problem is that I don't want this to run the instant I queue it up. I basically want to build the list, and then run it later. I am running the list using the reduce function in Q.js
{
return list.reduce(function(i, f) {
return i.then(f);
}, Q());
}
But this is kind of counter to my goal, since I really don't intend to run them at the same time they are queued. Is there a way to save the execution for later and still pass the deferred object through the function?
Update
I was asked what I expect the code to do, which is a fair question. I'll try to explain. The purpose of this is to split up the logic because I am using ASP.NET MVC, and so I have _Layout pages, and then normal views - so there is logic that cannot run until other things are completed, but some times that is on a per-page basis. This method was contrived to deal with that.
Essentially it works like this ...
Loader.js
This is, for lack of a better term or current implementation, a global object. I have plans to change that eventually, but one step at a time.
{
var Loader = {};
var list = [];
initialize = function() {
Q().then(step1).then(step2).then(process).then(finalStep);
};
queue = function(f) {
// push the given function to the list
};
process = function() {
return list.reduce(function(i,f){
return i.then(f);
}, Q());
};
step1 = function() { // generic example
// create a promise
return deferred.promise;
}; // other steps are similar to this.
return Loader;
}
_Layout
<head>
#RenderSection("scripts", false)
<script type="text/javascript">
// we have the loader object already
Loader.initialize();
</script>
</head>
Index
#section Scripts {
<script type="text/javascript">
Loader.promise(function(deferred){
// do something here.
deferred.resolve();
}));
</script>
}
You could use a closure.
queue(function(deferred) {
return function() {
// this is the actual function that will be run,
// but it will have access to the deferred variable
deferred.resolve();
};
});
I think you should do something like
var Loader = {
promise: function(construct) {
var deferred = Q.defer();
construct(deferred);
return deferred.promise;
},
queue: function(f) {
this.ready = this.ready.then(f);
},
ready: Q.Promise(function(resolve) {
window.onload = resolve; // or whatever you need to do here
// or assign the resolve function to Loader.initialize and call it later
})
};
Then Loader.queue() functions that return other promises.

jquery deferred fallback - possible scoping issue

I am trying to listen to an array of deferred requests for completion. I want to add in fallbacks so that if the initial url fails, then it will load a json file. (I've loaded the jsbin page here to stop any cross domain issues).
My original code was something like
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail(function() {
$.get("http://run.jsbin.com/")
.done(function() {
statsDeferred.resolve();
});
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
However it fails at the line statsDeferred.resolve();
http://jsbin.com/pofejotu/1/
I have tried adding in $.proxy calls to maintain scope but it isn't working.
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail($.proxy(function() {
$.get("http://run.jsbin.com/")
.done($.proxy(function() {
statsDeferred.resolve();
}, this));
}, this));
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
http://jsbin.com/vonibuhe/1/edit
Both fail on
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
If you want to chain promises, the correct method to use is .then() :
function makeCalls () {
var statsDeferred = $.get("http://thiswillfail.yesitwill");
statsDeferred = statsDeferred.then(
null, /* on success, keep the initial promise's state */
function(){ return $.get("http://run.jsbin.com/"); }
);
return statsDeferred;
}
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
The error you have is the difference between a Deferred and a Promise.
a Deferred exposes methods to change its inner state (.resolve and .reject),
a Promise only allows you to consult this state, and react on it (.done, .fail, ...)
API functions will generally return a Promise, so that external users cannot meddle with the expected state. As an example, one way to "fix" your code would be the following :
function makeCalls() {
// make a deferred, you will be the one in control of its state :
var deferred = $.Deferred();
var firstGet = $.get("http://thiswillfail.yesitwill");
firstGet.done(function(response) { deferred.resolve(response); })
// if the first request fails, run the second :
firstGet.fail(function(){
var secondGet = $.get("http://run.jsbin.com/");
secondGet.done(function(response) { deferred.resolve(response) };
secondGet.fail(function() { deferred.reject() });
});
// only return the Promise to the outer world :
return deferred.promise();
}

Javascript module pattern, ajax functions callbacks

var ajaxStuff = (function () {
var doAjaxStuff = function() {
//an ajax call
}
return {
doAjaxStuff : doAjaxStuff
}
})();
Is there any way to make use of this pattern, and fetch the response from a successful ajaxcall when calling my method? Something like this:
ajaxStuff.doAjaxStuff(successHandler(data){
//data should contain the object fetched by ajax
});
Hope you get the idea, otherwise I'll elaborate.
Two things:
1. Add a parameter to the doAjaxStuff function.
2. When invoking doAjaxStuff, pass in an anonymous function (or the name of a function)
var ajaxSuff = (function () {
var doAjaxStuff = function(callback) {
// do ajax call, then:
callback(dataFromAjaxCall);
}
return {
doAjaxStuff : doAjaxStuff
}
})();
// calling it:
ajaxStuff.doAjaxStuff(function(data){
//data should contain the object fetched by ajax
});
Just let doAjaxStuff accept a callback:
var doAjaxStuff = function(callback) {
// an ajax call
// Inside the Ajax success handler, call
callback(response); // or whatever the variable name is
}
Depending on your overall goals, you could also use deferred objects instead (or in addition). This makes your code highly modular. For example:
var doAjaxStuff = function() {
// $.ajax is just an example, any Ajax related function returns a promise
// object. You can also create your own deferred object.
return $.ajax({...});
}
// calling:
ajaxStuff.doAjaxStuff().done(function(data) {
// ...
});
I believe you need to read the jQuery docs for jQuery.ajax. You could make a call as simple as:
$.ajax('/path/to/file').success(function (data) {
doStuff();
})

Categories

Resources