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.
Related
I have a function processing an array, calling a function for each entry within the array with a Promise returned, and then finally returning that it's done.
var dealCardSelectableAI = function () {
var deferred = $.Deferred();
var deferredQueue = [];
_.forEach(model.galaxy.systems(), function (system, starIndex) {
if (model.canSelect(starIndex) && system.star.ai()) {
deferredQueue.push(
chooseCards({
inventory: inventory,
count: 1,
star: system.star,
galaxy: game.galaxy(),
addSlot: false,
}).then(function (result) {
system.star.cardList(result);
})
);
}
});
$.when.apply($, deferredQueue).then(function () {
deferred.resolve();
});
return deferred.promise();
};
dealCardSelectableAI().then( /* other stuff */ )
Most stuff I find on promises is to do with ES6 and Promises.all(), but I'm working in ES5 so am using jQuery.
My understanding is that due to deferredQueue being of unknown length $.when.apply() is the correct call here rather than $.when(), but regardless of which I use everything seems to operate as expected.
Have I set this up correctly? Is $.when.apply() the correct call and am I using it right?
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.
I am new to jquery deferred and I spent a while looking at similar questions on here but to no avail. I could use some help
I have a function that grabs the form input values and a function that saves those values. The form inputs values are not being grabbed fast enough before the save items function executes. I am trying to solve this by using jquery deferred but cannot get it to work. I also tried to use .when and .done.
When I hard coded the parameters in my js file, Attempt 2 worked but not when I passed parameters on an onclick or in my console.
Any help would be appreciated!
Attempt 1:
function deferredTest1(w,x,y,z) {
var d1 = $.Deferred();
d1.done(function() {
SaveNewItem(x,y,z);
});
function getFormValues(w) {
$(w).each(function(i,obj) {
var input = $(this).val();
var inputID = $(this).attr('id');
str = str.concat(",'"+inputID+"':'"+input+"'");
});
}
d1.resolve();
}
Attempt 2:
function deferredTest2(w,x,y,z) {
var formvalues = getFormValues(w);
$.when(formvalues).done(SaveNewItem(x,y,z));
}
Your second try was closest. The $.when call takes a deferred, so we create one first, then use that in the $.when call. It is resolved in the getFormValues method after the loop is completed. At this time the then callback is executed.
var deferred = $.Deferred();
function deferredTest3(w,x,y,z) {
$.when(deferred).then(function () {
SaveNewItem(x,y,z);
});
}
function getFormValues(w) {
$(w).each(function(i,obj) {
var input = $(this).val();
var inputID = $(this).attr('id');
str = str.concat(",'"+inputID+"':'"+input+"'");
});
deferred.resolve();
}
There is no asynchronous action here though, so a deferred is not necessary. A deferred is used to manage various asynchronous requests and their callbacks if they depend on one another.
I allmost banged my head into the wall because I can't get the following code too work. I'm trying to code a photo gallery with the flickrApi and have problems with multiple async calls. But perhaps there is a cleaner solution to code this.
openPhotoset() is called when clicking the link of a photoset. Unfortunately getting the description of a photo I need to use a different method, which means another async call. I'm looping through the data, but because I make the call in a loop (that's when I have the photo-id available) the deferred of openPhotoset() doesn't resolve after looping but before. I read and have seen examples of $.when() used in a loop, filling an array with deferreds and checking with $.when but I seem to fail horribly at it. Is this the solution I need or is there another road to salvation? ;)
I want to execute different functions after all calls within openPhotoset() has completed.
function openPhotoset(photosetId) {
var currentPhotoset = [],
deferred = $.Deferred();
_requestPhotosOfSet(photosetId).done(function(data){
$(data.photoset.photo).each(function(i, photo){
var objPhoto = {};
objPhoto.id = photo.id;
objPhoto.title = photo.title;
objPhoto.farm = photo.farm;
objPhoto.server = photo.server;
objPhoto.secret = photo.secret;
// get photo description
requestPhotoInfo(photo.id).done(function(data) {
objPhoto.description = data.photo.description._content;
currentPhotoset.push(objPhoto);
}).then(function() {
// TODO: renders with each iteration, shouldnt!
var template = $('#li-gallery').html(),
result = Mustache.render(template, {currentPhotoset:currentPhotoset});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
deferred.resolve();
});
});
});
return deferred;
}
You can do this by changing .done() for .then() in a couple of places, and rearranging things a bit - well quite
a lot.
I think you've probably been searching for something like this :
function openPhotoset(photosetId) {
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(function(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
return {
id: photo.id,
title: photo.title,
farm: photo.farm,
server: photo.server,
secret: photo.secret,
description: data.photo.description._content
};
});
}).get();//.get() is necessary to convert a jQuery object to a regular js array.
return $.when.apply(null, promises).then(function() {
var template = $('#li-gallery').html(),
result = Mustache.render(template, {
currentPhotoset: Array.prototype.slice.apply(arguments)
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
});
});
}
The main difference here is the creation of an array of promises as opposed to an array of photo objects, and allowing the promises to convey the data. This allows $.when() to fire off a callback when all the promises are fulfilled - ie when data objects have been composed for all photos in the set.
Note the use of .map() instead of .each(), thus simplifying the creation of promises.
And finally, the overall promise returned by openPhotoset() allows whatever action to be taken on completion of the whole process. Just chain .then().
openPhotoset(...).then(function() {
// here, do whatever
});
EDIT
The overall pattern is probably easier to understand if the inner workings are pulled out and rephrased as named promise-returning functions - getPhotoInfoObject() and renderData().
function openPhotoset(photosetId) {
function getPhotoInfoObject(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
//$.extend() is much less verbose than copying `photo`'s properties into a new object longhand.
return $.extend(photo, {description: data.photo.description._content});
});
}
function renderData() {
var template = $('#li-gallery').html(),
currentPhotoset = Array.prototype.slice.apply(arguments),
result = Mustache.render(template, {
currentPhotoset: currentPhotoset
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
}
// With the inner workings pulled out as getPhotoInfoObject() and renderData(),
// the residual pattern is very concise and easier to understand.
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(getPhotoInfoObject).get();
return $.when.apply(null, promises).then(renderData);
});
}
I was so blinded by the defereds and $.when function that I didn't notice all I needed was to create a counter and count down each time requestPhotoInfo was done and after render the html
So, I've built an api object that can be included in any JavaScript file via require.js. In the api object, I have calls to create Backbone models/collections like the one shown below:
getDatapoints: function(attributes, callback) {
require(['models/datapoint'], function(Datapoint){
var datapoint = new Datapoint.DatapointCollection(attributes);
datapoint.fetch({success: function(data){
return callback(data.toJSON());
}});
});
}
I want to have a way of being able to start multiple calls and run a callback function once ALL calls have completed. It looks like jQuery's $.when function does what I want, but I'm not sure how to get it to work with anything besides $.ajax calls.
Am I looking in the right place? Should I be looking at something like q.js?
Expanding on #mattacular's answer:
API = {
getDatapoints: function (attributes){
var dfd = $.Deferred();
require(['models/datapoint'], function(Datapoint){
var dataPoints = new Datapoint.DatapointCollection(attributes);
dataPoints.fetch().then(function (points){
dfd.resolve(points.toJSON());
}, function (error){
dfd.reject(error);
});
});
return dfd.promise();
},
getAllDatapoints: function (arrayOfAttributes){
var arrayOfPromises = arrayOfAttributes.map(this.getDatapoints);
return $.when.apply($, arrayOfPromises);
}
}
And where you're actually calling the getAllDatapoints method:
var allDatapointAttributes = [{...}, {...}, {...}];
API.getAllDatapoints(allDatapointAttributes).done(function(){
console.log.apply(console, arguments);
// should output an array of arrays containing dataPoint
// objects when all the requests have completed successfully.
});
You can do this using jQuery's Deferred object. Here is a quick example:
getDatapoints: function (attributes, callback() {
var dataPoints = $.Deferred();
// perform async calls here
// when "done," call dataPoints.resolve() or dataPoints.reject() accordingly
return dataPoints.promise();
}
edit: removed outdated tutorial