JQuery Deferred. Using $.when and .progress() - javascript

I am writing a parser littered with Async Tasks. I use JQuery promises to control and order the async tasks. This is a psuedo code version of the constructor function:
/**
* #constructor
*/
function Parser(json)
{
return $.when(
this.delete().then(this.insert(json)),
this.doSomething(),
this.doSomethingElse().then(this.update(json))
)
};
and this is what an example function looks like:
Parser.prototype.doSomething = function()
{
var dfd = $.Deferred();
exampleTask(dfd.reject, dfd.resolve);
return dfd.promise();
};
From the JQuery docs:
In the case where multiple Deferred objects are passed to jQuery.when,
the method returns the Promise from a new "master" Deferred object
that tracks the aggregate state of all the Deferreds it has been
passed
How can I use .progress() to notify anybody that cares about the overall progress of the Parser?
e.g.
var parser = new Parser(json);
parser.progress(function(prog){console.log(prog});
Heres a fiddle with what I'm trying to do:
http://jsfiddle.net/ashanova/RjULA/2/

Use deferred.notify() to call the progressCallbacks. For example:
function doSomething() {
var dfd = $.Deferred();
var count = 0;
var intervalId = setInterval(function() {
dfd.notify(count++);
count > 3 && clearInterval(intervalId);
}, 500);
return dfd.promise();
};
var promise = doSomething();
promise.progress(function(prog) {
console.log(prog);
});​
DEMO.

Related

How can I redirect a promise to an existing deferred?

I have a function which returns a promise. I create a jQuery deferred for this purpose, which might be resolved/rejected in custom ways, depending on implementation.
One implementation uses an AJAX call, and there I'd like to redirect or queue the failure/resolution of the AJAX promise to the one which was created earlier. This means that whenever the AJAX call has a resolve/reject/progress, the deferred should trigger its own resolve/reject/progress too with the same arguments.
Here is some dummy sample code.
function Test() {
}
Test.prototype.doSomething() {
this._deferred = $.Deferred();
this.doSomethingImpl();
return this._deferred;
}
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
// resolve/reject/progress here the this._deferred based on the ajax promise
}
I know I can do it in a verbose way using the AJAX done, fail and progress callbacks, and manually call my deferred's corresponding method (resolve, reject or progress), but I'm seeking for kind of a one-liner, if there is any.
EDIT
Here is a code which is similar to the real one, using knockoutjs.
function GridViewModel() {
var self = this;
self.pageIndex = ko.observable(0);
...
self._refreshRequest = ko.observable(null).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });
self._doRefresh = function() {
$.ajax(...)
.done(result) { // update rows, etc. }
.then(
function(r) { self._refreshPromise.resolve(r); },
function(r) { self._refreshPromise.reject(r); },
function(r) { self._refreshPromise.progress(r); }
)
.always(function() { self._refreshPromise = null; }
// here I used the obvious verbose redirecting
}
...
ko.computed(function() {
var pageIndex = self.pageIndex();
if (ko.computedContext.isInitial()) return;
this.refreshRequest("Paging");
});
ko.computed(function() {
var refreshRequest = self.refreshRequest();
if (ko.computedContext.isInitial() || !refreshRequest) return;
self._doRefresh(refreshRequest);
}
}
GridViewModel.prototype.Refresh = function(type) {
this._refreshPromise = this._refreshPromise || $.Deferred();
this._refreshRequest(type);
return this._refreshPromise;
}
This code is a snippet of a complex data grid viewmodel class, and the fancy refresh solution is there to ensure that refreshing is throttled.
Yes, it would be possible to redirect the resolution (in a perfect world1, just deferred.resolve(promise)), but it's completely unnecessary. Don't create deferreds when you're already calling something that produces a promise for you - avoid the deferred antipattern! You can simply return that very promise:
Test.prototype.doSomething = function() {
return this.doSomethingImpl();
};
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
return ajax; // the promise
};
1) where jQuery follows the Promises/A+ specification and deferred.resolve accepts thenables

Resolve deferred object after other multiple deferred objects are resolved

My experience with $.Deferred is very limited, and so far the code the came looks very messy.
The function that is supposed to return a promise is updating the DOM with HTML received after an ajax request.
It's used like this:
this._refreshWorkspace(response.html).then(function(){
// do things after DOM update finished
});
Here's the function code:
_refreshWorkspace: function(htmlBlocks){
var dfd = $.Deferred();
if('editor' in htmlBlocks){
app.destroy(this.editor).then((function(){
this.editor.empty().append(htmlBlocks.editor);
}).bind(this)).then((function(){
app.refresh(this.editor);
}).bind(this));
}
if('listPanels' in htmlBlocks){
app.destroy(this.list).then((function(){
this.list.empty().append(htmlBlocks.listPanels);
}).bind(this)).then((function(){
app.refresh(this.list);
// other unrelated code here
dfd.resolve();
}).bind(this));
}
if('listNav' in htmlBlocks){
// similar code block
}
return dfd;
},
It seems to work but only if the "listPanels" htmlBlock is provided.
I want the dfd to be resolved once after all refresh calls, or even better if possible - after all refresh calls are resolved. Any ideas on how could I make this happen?
Put all of the promises from the loops into an array, then use $.when. Sadly, using $.when with an array is ugly:
return $.when.apply($, theArray);
...because $.when is designed to accept discrete arguments rather than an array.
Something like this:
_refreshWorkspace: function(htmlBlocks){
var promises = [];
if('editor' in htmlBlocks){
promises.push(
app.destroy(this.editor).then((function(){
this.editor.empty().append(htmlBlocks.editor);
}).bind(this)).then((function(){
app.refresh(this.editor);
}).bind(this))
);
}
if('listPanels' in htmlBlocks){
promises.push(
app.destroy(this.list).then((function(){
this.list.empty().append(htmlBlocks.listPanels);
}).bind(this)).then((function(){
app.refresh(this.list);
}).bind(this))
);
}
if('listNav' in htmlBlocks){
// similar code block
}
return $.when.apply($, promises);
},
Here's a live example using random Deferreds:
function doSomething() {
var promises = [];
var d1, d2, d3;
d1 = new $.Deferred();
promises.push(d1.promise());
setTimeout(function() {
snippet.log("Resolving d1");
d1.resolve(1);
}, Math.floor(Math.random() * 1000));
d2 = new $.Deferred();
promises.push(d2.promise());
setTimeout(function() {
snippet.log("Resolving d2");
d2.resolve(2);
}, Math.floor(Math.random() * 1000));
d3 = new $.Deferred();
promises.push(d3.promise());
setTimeout(function() {
snippet.log("Resolving d3");
d3.resolve(3);
}, Math.floor(Math.random() * 1000));
return $.when.apply($, promises);
}
// Use it
doSomething().then(function() {
snippet.log("All resolved");
});
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
As already explained, the simple answer to the question is to aggregate the individual promises with $.when.apply(null, promiseArray).
However, if all the code in the question is representative of the treatment to be applied to all html blocks, then you can go further.
jQuery.map() will operate on ownProperties of an object so can be exploited to iterate over htmlBlocks, resulting in a concise, generalised main routine with a couple of supporting hashes.
_refreshWorkspace: function(htmlBlocks) {
var that = this; // avoids the need for .bind(this) in the promise chain and the methodsHash
var propHash = {
'editor': 'editor',
'listPanels': 'list'
};
// All the "other unrelated code" is defined here
var methodsHash = {
'editor': null,
'listPanels': function(key, obj) {
...
},
...
};
//And the main routine is a concise $.map(htmlBlocks, ...) structure.
var promises = $.map(htmlBlocks, function(html, key) {
var obj = that[propHash[key]];
return app.destroy(obj).then(function() {
obj.empty().append(html); //if empty() and append() are jQuery methods then this line is synchronous.
return app.refresh(obj);// if app.destroy() is asynch and theanable, then it seems safe to assume that app.refresh() is also asynch and theanable. Therefore return the result here.
}).then(function() {
if(methodsHash[key]) {
methodsHash[key](key, obj);
}
});
});
//Now aggregate `promises` into a single promise which resolves when all the promises resolve, or rejects when any of the promises rejects.
return $.when.apply(null, promises);
},
Now, to cater for all other html blocks, just add one line to propHash and a null or function to methodsHash. Providing the main routine is comprehensive, it won't need amending.
IMHO, this a better way to organise the code.

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();
}

JQuery Waiting for multiple deferreds to resolve

I have the following function which queries a SQLite database and pushed results into an Array that is used later. I need to wait for the each statement to process the tables in the (self.projectSetEditList) However it seems that the master deferred is not waiting for all promises... Am I just going about this all wrong? I need to know when all the sql results are ready before proceeding to the next function.
/// <summary>Validates user INSERTS to project_set entities</summary>
this.initProjectSetAdds = function ()
{
var promises = [];
var masterDeferred = new $.Deferred();
///var count = (self.projectSetEditList.length - 1);
$.each(self.projectSetEditList, function (index, syncEntity)
{
var def = new $.Deferred();
//get the config entity definition object
var entityDefinition = self.getEntityDefinition(syncEntity.entity_name);
self.db.executeSql(self.getAddsSql(entityDefinition)).done(function (tx, insertResults)
{
self.projectSetAdds.push({ definition: entityDefinition, addedObjects: dataUtils.convertToObjectArray(insertResults) });
def.resolve(true);
});
promises.push(def);
});
//resolve all deferred and return to caller
$.when.apply($, promises).then(function ()
{
masterDeferred.resolve(arguments);
},
function ()
{
masterDeferred.reject(arguments);
});
return (masterDeferred.promise());
}
The only async function inside is executeSql... Any suggestions are greatly appreciated
Your code appears unnecessarily complicated to me.
Since $.when also creates a promise, don't bother creating the masterDeferred yourself, just do:
return $.when.apply($, promises);
The only functional difference is that this version will pass the true results as individual parameters to the eventual callback, whereas your code will pass a single array of [true, true, ...] values.

Jquery .when and multiple .load

I want to have one callback function after actions are done, I'm trying something like this:
$.when(
$('#detail1').load('/getInfo.php'),
$('#detail2').load('/getOther.php')
).then(function(a,b){
alert("done");
});
The problem is that the callback function is firing before the actions are finished.
This is because jQuery.when() expects jQuery.Deferred instances while load() returns an jQuery instance (see http://api.jquery.com/jQuery.when/ and http://api.jquery.com/load/).
You can work around this issue:
// Create two Deferred instances that can be handed to $.when()
var d1 = new $.Deferred();
var d2 = new $.Deferred();
// Set up the chain of events...
$.when(d1, d2).then(function() {
alert('done');
});
// And finally: Make the actual ajax calls:
$('#detail1').load('/getInfo.php', function() { d1.resolve(); });
$('#detail2').load('/getOther.php', function() { d2.resolve(); });
I do a similar code but for images in a more dynamic way. Hope that it help.
var deferreds = [];
// Create a deferred for all images
$('.my-img-class').each(function() {
deferreds.push(new $.Deferred());
});
var i = 0;
// When image is loaded, resolve the next deferred
$('.my-img-class').load(function() {
deferreds[i].resolve();
i++;
});
// When all deferreds are done (all images loaded) do some stuff
$.when.apply(null, deferreds).done(function() {
// Do some stuff
});

Categories

Resources