I'm writing an engine that requires the use of getScript quite extensively. I've pushed it into its own function, for ease of use, but now I need to make sure that the function itself is synchronous. Unfortunately, I can't seem to make getScript wait until the script it loads is actually finished loading before proceeding. I've even tried setting jQuery's ajax asynch property to false before making the call. I'm thinking of using jQuery's when/done protocol, but I can't seem to wrap my head around the logic of placing it inside a function and making the function itself synchronous. Any help would be very much appreciated!
function loadScript(script){
//Unrelated stuff here!!!
$.when(
$.getScript(script,function(){
//Unrelated stuff here!!!
})).done(function(){
//Wait until done, then finish function
});
}
Loop code (by request):
for (var i in divlist){
switch($("#"+divlist[i]).css({"background-color"})){
case #FFF:
loadScript(scriptlist[0],divlist[i]);
break;
case #000:
loadScript(scriptlist[2],divlist[i]);
break;
case #333:
loadScript(scriptlist[3],divlist[i]);
break;
case #777:
loadScript(scriptlist[4],divlist[i]);
break;
}
}
This worked for me, and may help you.
$.ajax({
async: false,
url: "jui/js/jquery-ui-1.8.20.min.js",
dataType: "script"
});
Basically, I just bypassed the shorthand notation and added in the async: false
As I said, it's relatively easy to chain Ajax calls with promise objects. Now, it don't see why the scripts have to be loaded one after the other, but you will have a reason for it.
First though I would get rid of the switch statement if you are only calling the same function with different arguments. E.g. you can put all the script URLs in a map:
var scripts = {
'#FFF': '...',
'#000': '...'
// etc.
};
You can chain promises by simply returning another promise from a callback passed to .then [docs]. All you need to do is start with a promise or deferred object:
var deferred = new $.Deferred();
var promise = deferred.promise();
for (var i in divlist) {
// we need an immediately invoked function expression to capture
// the current value of the iteration
(function($element) {
// chaining the promises,
// by assigning the new promise to the variable
// and returning a promise from the callback
promise = promise.then(function() {
return loadScript(
scripts[$element.css("background-color")],
$element
);
});
}($('#' + divlist[i])));
}
promise.done(function() {
// optional: Do something after all scripts have been loaded
});
// Resolve the deferred object and trigger the callbacks
deferred.resolve();
In loadScript, you simply return the promise returned from $.getScript or the one returned by .done:
function loadScript(script_url, $element){
// Unrelated stuff here!!!
return $.getScript(script_url).done(function(){
// Unrelated stuff here
// do something with $element after the script loaded.
});
}
The scripts will all be called in the order the are access in the loop. Note that if divlist is an array, you really should use normal for loop instead of a for...in loop.
Do you know that $.getScript accepts a callback function that is called synchronously after the script is loaded?
Example:
$.getScript(url,function(){
//do after loading script
});
I have 2 more solutions: a pure js one and one for multiple js load.
Try this way, create array with deferred objects and used $.when with "apply"
var scripts = [
'src/script1.js',
'src/script2.js'
];
var queue = scripts.map(function(script) {
return $.getScript(script);
});
$.when.apply(null, queue).done(function() {
// Wait until done, then finish function
});
var getScript = function(url) {
var s = document.createElement('script');
s.async = true;
s.src = url;
var to = document.getElementsByTagName('script')[0];
to.parentNode.insertBefore(s, to);
};
#Felix Kling's answer was a great start. However, I discovered that there was a slight issue with the overall attached .done() at the end of the .getScripts() returned result if I wanted to "functionalize" it. You need the last promise from the chained .getScript() iterations from within the loop. Here's the modified version of his solution (thank you, BTW).
Plugin:
(function ($) {
var fetched = new function () {
this.scripts = [];
this.set = [];
this.exists = function (url) {
var exists = false;
$.each(this.set, function (index, value) {
if ((url || '') === value) {
exists = true;
return false;
}
});
return exists;
};
this.buildScriptList = function () {
var that = this;
that.set = [];
$('script').each(function () {
var src = $(this).attr('src') || false;
if (src) {
that.set.push(src);
}
});
$.merge(this.set, this.scripts);
return this;
};
},
getScript = $.getScript;
$.getScript = function () {
var url = arguments[0] || '';
if (fetched.buildScriptList().exists(url)) {
return $.Deferred().resolve();
}
return getScript
.apply($, arguments)
.done(function () {
fetched.scripts.push(url);
});
};
$.extend({
getScripts: function (urls, cache) {
if (typeof urls === 'undefined') {
throw new Error('Invalid URL(s) given.');
}
var deferred = $.Deferred(),
promise = deferred.promise(),
last = $.Deferred().resolve();
if (!$.isArray(urls)) {
urls = [urls];
}
$.each(urls, function (index) {
promise = promise.then(function () {
last = $.getScript(urls[index]);
return last;
});
});
if (Boolean(cache || false) && !Boolean($.ajaxSetup().cache || false)) {
$.ajaxSetup({cache: true});
promise.done(function () {
$.ajaxSetup({cache: false});
});
}
deferred.resolve();
return last;
}
});
})($);
You can ignore the fetched function (I implemented it to reduce potential redundant calls - which is why I hijacked .getScript()) and see where the variable last is set inside the .getScripts() method. It defaults to a resolved deferred object, so that if the urls array is empty, it's passed to the returned result to attach the outer .done() call to. Otherwise, it will inevitably be assigned the last promise object from the chained .getScript() calls and thus will ensure everything will remain synchronous from outside the function.
Returning the initially created deferred object will not work if you resolve it before returning it back to the invoker (which is what you're supposed to do per jQuery's official documentation).
Example:
function loadStuff(data) {
var version = {
'accounting': '1.2.3',
'vue': '1.2.3',
'vueChart': '1.2.3'
};
$.getScripts([
'https://cdnjs.cloudflare.com/ajax/libs/accounting.js/' + version.accounting + '/accounting.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue/' + version.vue + '/vue.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue-chartjs/' + version.vueChart + '/vue-chartjs.min.js'
], true)
.done(function () {
// do stuff
})
.fail(function () {
throw new Error('There was a problem loading dependencies.');
});
}
Just create a script node, set its src property to the JS you want to load then append it to the head:
var myScript = document.createElement('script');
myScript.src = "thesource.js";
document.head.appendChild(myScript);
this is what I do
function loadJsFile(filename) {
$.ajaxSetup({
cache: true
});
var dloadJs = new $.Deferred();
$.when(dloadJs).done(function () {
$.ajaxSetup({
cache: false
});
});
dloadJs.resolve(
$.getScript(filename, function () { })
);
}
Related
I have tried to execute 4 function in a row inside dojo domready block and also i want to set a Boolean flag after the execution of these functions, but all function are fired in order asynchronously and the flag is set before the completion of these function i.e. the functions doesn't wait for the previous function to complete, they all are just started and move to the next one.
require([ 'dojo/domReady!' ], function() {
boolean flag=false;
function1();
function2();
function3();
function4();
flag=true;
});
How to set the flag only after the execution of all the 4 functions
I am sharing my original code, first i am executing the initial call in dojo ready, where i am setting the flag onload to false first
require([ 'dojo/domReady!' ], function() {
onload = false;
getQuoteOption();
});
then in the function 'getQuoteOption()' i am firing an ajax call to get fetch some json data
function getQuoteOption(){
var reqParams = addQuoteReqParams(reqParams);//getting json input data
var request = require("dojo/request");
request.get(url, {
query : {
inputJson : reqParams
},
handleAs : "json",
preventCache : true
}).then(function(response) {
configureQuoteFieldData(response);
configureIrregularFrequencyData(response);
onload=true;
}, function(error) {
console.log(error);
});
}
in the callback of the ajax call i am executing two functions 'configureQuoteFieldData(response)' and 'configureIrregularFrequencyData(response)' and then setting the flag onload to true believing the two former functions have executed completely but the flag onload is set to true before that.
for referring i am listing two function also here
function configureQuoteFieldData(quoteFieldData) {
var registry = require("dijit/registry");
registry.byId('form_quoteData').set('value', quoteFieldData);//setting data to form
}
function configureIrregularFrequencyData(obj) {
var tmpArray = [];
for (var i in obj) {
tmpArray.push(obj[i]);
}
irregularPayMonths['irregularData'] = tmpArray;//saving to global variable
}
You have to write your functions to accept a callback or return a promise. Then trigger the subsequent function with that instead of just calling it immediately.
Consider using dojo/promise/all and dojo/Deferred, the idea is to use deferred objects as mentioned by #Quentin.
Below a live example using specifically dojo framwork.
https://jsfiddle.net/9khdr4qa/
Explanation:
The code runs 3 functions which will return asynchronously, each function return at a different time as indicated by setTimeout().
Now you wish to set your flag to true, only after all 3 functions are returned and done.
The solution include a deferred object in each function. When the function return the deferred object is Fulfilled.
Use dojo/promise/all to detect when all 3 functions are returned. Basically its callback will run only after all 3 deferred objects are marked as Fulfilled.
Than set you flag to true and carry on with your code.
You can read more about dojo promises and deferred object here:
http://dojotoolkit.org/reference-guide/1.10/dojo/promise.html
https://dojotoolkit.org/reference-guide/1.10/dojo/Deferred.html
require(["dojo/promise/all", "dojo/Deferred", "dojo/dom", "dojo/on", "dojo/json", "dojo/domReady!"],
function(all, Deferred, dom, on, JSON) {
var flag = false;
function requestA() {
var deferred = new Deferred();
setTimeout(function() {
deferred.resolve("requestA");
}, 500);
return deferred.promise;
}
function requestB() {
var deferred = new Deferred();
setTimeout(function() {
deferred.resolve("requestB");
}, 750);
return deferred.promise;
}
function requestC() {
var deferred = new Deferred();
setTimeout(function() {
deferred.resolve("requestC");
}, 1000);
return deferred.promise;
}
on(dom.byId("startButton"), "click", function() {
dom.byId("output").innerHTML = "Running...";
all([requestA(), requestB(), requestC()]).then(function(results) {
dom.byId("output").innerHTML = JSON.stringify(results);
flag = true;
alert('Your flag is set to: ' + flag);
});
});
});
<h1>Click to start:</h1>
<pre id="output"></pre>
<button type="button" id="startButton">Start</button>
With the code snippet shared, the flag will be set after executing all the 4 functions. But, that does not mean that it has completed executing all the logic within those function. If there are some asynchronous calls within those functions then they will be executed after the flag is set.
If you could share the code within those functions, then we could give more specific solution.
UPDATE:
I believe the code for getting the registry, may be causing the problem.
var registry = require("dijit/registry");
The require gets the js file if it is not already retrieved from the server. that might be what is happening in your case. Try to have only one single require per script file. that way all the required files are retrieved before execution.
require(["dijit/registry","dojo/request", "dojo/domReady!"], function(registry, request) {
function getQuoteOption(){
var reqParams = addQuoteReqParams(reqParams);//getting json input data
request.get(url, {
query : {
inputJson : reqParams
},
handleAs : "json",
preventCache : true
}).then(function(response) {
configureQuoteFieldData(response);
configureIrregularFrequencyData(response);
onload=true;
}, function(error) {
console.log(error);
});
}
function configureQuoteFieldData(quoteFieldData) {
registry.byId('form_quoteData').set('value', quoteFieldData);//setting data to form
}
function configureIrregularFrequencyData(obj) {
var tmpArray = [];
for (var i in obj) {
tmpArray.push(obj[i]);
}
}
onload = false;
getQuoteOption();
});
Make sure the order of module and corresponding function parameters are matching.
my function didnt wait to complete earlier function execution and it is completing.
I have my code is that i am doing some thing wrong:
$scope.abc1 = function(){
var arrayJson = [{'name':'max','age':'12'},{'name':'tax','age':'16'}]
for(var i=0; i<arratJson.length;i++){
var getAddress = $scope.xyz(arratJson[i].name);
}
$scope.createBody();
};
$scope.createBody = function(){
//some more code here
};
$scope.xyz = function(name){
$http({
method: 'GET',
url: 'rest/address',
type:'json',
headers:{'action':'single','name':name},
}).success(function(response){
return response;
}).error(function(response){
});
};
so in this it is not waiting to get address instead of it moving down so how to wait finishing for loop and then call different function.
createBody function called before the $scope.xyz() function returns the value how to wait until loop finishes
This is expected due to asynchronous nature of execution. You should use callbacks to avoid this problem.
You should use the $q service.
first store all $http calls in an array and with $q.all(array) you create a promise that is resolved after all $http promises have resolved.
e.g:
$scope.abc1 = function(){
var arrayJson = [{'name':'max','age':'12'},{'name':'tax','age':'16'}]
var promises = [];
for(var i=0; i<arratJson.length;i++){
promises.push($scope.xyz(arratJson[i].name));
}
$q.all(promises).then($scope.createBody);
};
And on the resolve of this new promise you can call your createBody function.
For this to work You should also change success callback on the $scope.xyz to a then callback and return the promise.
e.g:
$scope.xyz = function(name){
return $http({
method: 'GET',
url: 'rest/address',
type:'json',
headers:{'action':'single','name':name},
}).then(function(response){
return response;
})["catch"](function(response){
});
};
UPDATE
If you don't care if all calls succeed, replace:
$q.all(promises).then($scope.createBody);
With:
$q.all(promises)["finally"]($scope.createBody);
PS: Keep in mind that in the finally callback you don't get the return values of every call, whereas in the then an array will be passed as argument in the callback function which holds in every position a return value of each $http call.
There are two way to achieve this
1)Use async:false
2)Need to use callback
choose your way and enjoy!
You should how promises works in javascript.
$http is an asynchronous function. You must return $http result in $scope.xyz function and use then, success, error function callbacks.
Example:
function xyz() {
return $http(...);
}
xyz().then(function(data) {
address = data.data.address; // in your json dto format
})
more info https://docs.angularjs.org/api/ng/service/$http
hope this helps!
regards
You can use Angular promises.
Include promise status to self created object with deferred value, that has valueOf and toString methods. Last two methods allow to use arithmetic, string and comparison operators.
Object with deferred value:
var DeferredValue = function(initial){
var self = this;
self._value = initial;
var deferred = $q.defer();
self.$promise = deferred.promise;
self.$resolved = false;
self.$resolve = function(value){
deferred.resolve(value);
}
self.$promise.then(function(v){
self._value = v;
deferred = null;
self.$resolved = true;
delete self.$resolve;
return self.$promise;
});
}
DeferredValue.prototype = {
constructor: DeferredValue,
valueOf: function(){
return this._value
},
toString: function(){
return this._value.toString()
}
}
Return this object in your ASYNC function, and resolve them after retrieving data:
var getValue = function(){
var value = new DeferredValue();
$timeout(function(){
value.$resolve(Math.floor(Math.random() * 10))
},1500);
return value;
}
Plunker example
Apologies for what I'm sure is a repost; I really have looked quite widely for an answer to my question (that I also understood).
What I'm trying to learn to do is to arbitrarily chain functions such that they must complete before the next occurs, which, as I understand it, is the purpose of jQuery's deferred(). So in the below code, what I'm imagining should happen is:
the function contained within the load deferred objects executes; after which
the function contained in then() executes; after which
the function contained in done() executes.
Every tutorial in the universe uses a $.ajax() object after $.when(), which is useless if all one wants is control of execution sequence in a local context.
Here's what I've been trying:
var preloadDone = false,
var testDone = false,
var load = $.deferred(function() {
//cacheImages() is a plugin, works fine
$("img.image-loader.preload").cacheImages();
preloadDone = true;
});
var loader = $.when(load)
.then(function() {
if (preloadDone) {
console.log("then called in sequence");
} else {
console.log("then called out of sequence"); // wrong order, every time
}
XBS.cache.cbDone = true;
}).done(function() {
if (XBS.cache.cbDone) {
console.log("even done was called in right sequence!"); // proper order, every time
} else {
console.log("done() wasn't called in order..");
}
});
load.resolve(); // nothing happens
// load(); also tried this; nothing happens
So far as I can tell, this is identical to the example given in the jQuery $.when() documentation. Lil help?
Here is a demo on how to run many functions one after another but only after each funtion has completed. This is achieved by using an Async function.
Demo (Runs 3 functions one after the other. Where i have alert("starting *") that is were you want to put the work you like to do and in the done function you include the next function you want to run. )
http://jsfiddle.net/5xLbk91c/
//the Assync function. Pause is the time in miliseconds to pause between loops
var asyncFor = function(params) {
var defaults = {
total: 0,
limit: 1,
pause: 10,
context: this
},
options = $.extend(defaults, params),
def = $.Deferred(),
step = 0,
done = 0;
this.loop = function() {
if (done < options.total) {
step = 0;
for (; step < options.limit; step += 1, done += 1) {
def.notifyWith(options.context, [done]);
}
setTimeout.apply(this, [this.loop, options.pause]);
} else {
def.resolveWith(options.context);
}
};
setTimeout.apply(this, [this.loop, options.pause]);
return def;
};
function one() {
asyncFor({
total: 1, // run only once. If you want to loop then increase to desired total.
context: this
}).progress(function(step) {
alert("starting one")
}).done(function() {
alert("finished one")
two()
});
}
function two() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting two")
}).done(function() {
alert("finished two")
three()
});
}
function three() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting three")
}).done(function() {
alert("finished three and all done")
});
}
you may want to start your investigations by this change to your code:
var load = function() {
var deferred = $.Deferred();
$("img.image-loader.preload").cacheImages();
preloadDone = true;
return deferred;
};
Please also note you may pass array of promises to $.when().
Best regards
Here's an contrived example of what's going on: http://jsfiddle.net/adamjford/YNGcm/20/
HTML:
Click me!
<div></div>
JavaScript:
function getSomeDeferredStuff() {
var deferreds = [];
var i = 1;
for (i = 1; i <= 10; i++) {
var count = i;
deferreds.push(
$.post('/echo/html/', {
html: "<p>Task #" + count + " complete.",
delay: count
}).success(function(data) {
$("div").append(data);
}));
}
return deferreds;
}
$(function() {
$("a").click(function() {
var deferreds = getSomeDeferredStuff();
$.when(deferreds).done(function() {
$("div").append("<p>All done!</p>");
});
});
});
I want "All done!" to appear after all of the deferred tasks have completed, but $.when() doesn't appear to know how to handle an array of Deferred objects. "All done!" is happening first because the array is not a Deferred object, so jQuery goes ahead and assumes it's just done.
I know one could pass the objects into the function like $.when(deferred1, deferred2, ..., deferredX) but it's unknown how many Deferred objects there will be at execution in the actual problem I'm trying to solve.
To pass an array of values to any function that normally expects them to be separate parameters, use Function.prototype.apply, so in this case you need:
$.when.apply($, my_array).then( ___ );
See http://jsfiddle.net/YNGcm/21/
In ES6, you can use the ... spread operator instead:
$.when(...my_array).then( ___ );
In either case, since it's unlikely that you'll known in advance how many formal parameters the .then handler will require, that handler would need to process the arguments array in order to retrieve the result of each promise.
The workarounds above (thanks!) don't properly address the problem of getting back the objects provided to the deferred's resolve() method because jQuery calls the done() and fail() callbacks with individual parameters, not an array. That means we have to use the arguments pseudo-array to get all the resolved/rejected objects returned by the array of deferreds, which is ugly:
$.when.apply($,deferreds).then(function() {
var objects = arguments; // The array of resolved objects as a pseudo-array
...
};
Since we passed in an array of deferreds, it would be nice to get back an array of results. It would also be nice to get back an actual array instead of a pseudo-array so we can use methods like Array.sort().
Here is a solution inspired by when.js's when.all() method that addresses these problems:
// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
jQuery.when.all = function (deferreds) {
return $.Deferred(function (def) {
$.when.apply(jQuery, deferreds).then(
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that succeeded. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.done:
// ( data, textStatus, jqXHR )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.resolveWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
},
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that failed. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.fail:
// ( jqXHR, textStatus, errorThrown )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.rejectWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
});
});
}
}
Now you can simply pass in an array of deferreds/promises and get back an array of resolved/rejected objects in your callback, like so:
$.when.all(deferreds).then(function(objects) {
console.log("Resolved objects:", objects);
});
You can apply the when method to your array:
var arr = [ /* Deferred objects */ ];
$.when.apply($, arr);
How do you work with an array of jQuery Deferreds?
When calling multiple parallel AJAX calls, you have two options for handling the respective responses.
Use Synchronous AJAX call/ one after another/ not recommended
Use Promises' array and $.when which accepts promises and its callback .done gets called when all the promises are return successfully with respective responses.
Example
function ajaxRequest(capitalCity) {
return $.ajax({
url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
success: function(response) {
},
error: function(response) {
console.log("Error")
}
});
}
$(function(){
var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
$('#capitals').text(capitalCities);
function getCountryCapitals(){ //do multiple parallel ajax requests
var promises = [];
for(var i=0,l=capitalCities.length; i<l; i++){
var promise = ajaxRequest(capitalCities[i]);
promises.push(promise);
}
$.when.apply($, promises)
.done(fillCountryCapitals);
}
function fillCountryCapitals(){
var countries = [];
var responses = arguments;
for(i in responses){
console.dir(responses[i]);
countries.push(responses[i][0][0].nativeName)
}
$('#countries').text(countries);
}
getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h4>Capital Cities : </h4> <span id="capitals"></span>
<h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>
As a simple alternative, that does not require $.when.apply or an array, you can use the following pattern to generate a single promise for multiple parallel promises:
promise = $.when(promise, anotherPromise);
e.g.
function GetSomeDeferredStuff() {
// Start with an empty resolved promise (or undefined does the same!)
var promise;
var i = 1;
for (i = 1; i <= 5; i++) {
var count = i;
promise = $.when(promise,
$.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + count + " complete.",
delay: count / 2
},
success: function (data) {
$("div").append(data);
}
}));
}
return promise;
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});
Notes:
I figured this one out after seeing someone chain promises sequentially, using promise = promise.then(newpromise)
The downside is it creates extra promise objects behind the scenes and any parameters passed at the end are not very useful (as they are nested inside additional objects). For what you want though it is short and simple.
The upside is it requires no array or array management.
I want to propose other one with using $.each:
We may to declare ajax function like:
function ajaxFn(someData) {
this.someData = someData;
var that = this;
return function () {
var promise = $.Deferred();
$.ajax({
method: "POST",
url: "url",
data: that.someData,
success: function(data) {
promise.resolve(data);
},
error: function(data) {
promise.reject(data);
}
})
return promise;
}
}
Part of code where we creating array of functions with ajax to send:
var arrayOfFn = [];
for (var i = 0; i < someDataArray.length; i++) {
var ajaxFnForArray = new ajaxFn(someDataArray[i]);
arrayOfFn.push(ajaxFnForArray);
}
And calling functions with sending ajax:
$.when(
$.each(arrayOfFn, function(index, value) {
value.call()
})
).then(function() {
alert("Cheer!");
}
)
If you're transpiling and have access to ES6, you can use spread syntax which specifically applies each iterable item of an object as a discrete argument, just the way $.when() needs it.
$.when(...deferreds).done(() => {
// do stuff
});
MDN Link - Spread Syntax
I had a case very similar where I was posting in an each loop and then setting the html markup in some fields from numbers received from the ajax. I then needed to do a sum of the (now-updated) values of these fields and place in a total field.
Thus the problem was that I was trying to do a sum on all of the numbers but no data had arrived back yet from the async ajax calls. I needed to complete this functionality in a few functions to be able to reuse the code. My outer function awaits the data before I then go and do some stuff with the fully updated DOM.
// 1st
function Outer() {
var deferreds = GetAllData();
$.when.apply($, deferreds).done(function () {
// now you can do whatever you want with the updated page
});
}
// 2nd
function GetAllData() {
var deferreds = [];
$('.calculatedField').each(function (data) {
deferreds.push(GetIndividualData($(this)));
});
return deferreds;
}
// 3rd
function GetIndividualData(item) {
var def = new $.Deferred();
$.post('#Url.Action("GetData")', function (data) {
item.html(data.valueFromAjax);
def.resolve(data);
});
return def;
}
If you're using angularJS or some variant of the Q promise library, then you have a .all() method that solves this exact problem.
var savePromises = [];
angular.forEach(models, function(model){
savePromises.push(
model.saveToServer()
)
});
$q.all(savePromises).then(
function success(results){...},
function failed(results){...}
);
see the full API:
https://github.com/kriskowal/q/wiki/API-Reference#promiseall
https://docs.angularjs.org/api/ng/service/$q
I'm trying to get jQuery deferred to work and am having some problems.
My system is using postMessage to pass messages from and to sandboxed areas on a website. So when I request a service from a sandboxed area like this:
// on click of button
foo.requestService(options, function (response) {
$("#c").val(response); // set value of input button
}
Internally, I'm creating a new $.deferred every time the button is clicked and the service is requested like so:
that.requestService = $.fn.requestService = function (options, callbackFunction) {
var deferred = new $.Deferred(),
callbackId = priv.generateUuid(),
callback = deferred;
// store callback to be retrieved by response handler
priv.trackCallback(callbackId, callback, callbackFunction);
// set type
if (options.type === undefined) {
options.type = "request/any";
}
// the callback function to be run, once a postMessage
// with the response is received
deferred.done(function(result, callbackFunction) {
if (callbackFunction) {
callbackFunction(result);
}
});
// trigger messaging
window.top.postMessage(options, window.location.href);
};
All works well, I can request my sandboxed service, run it, postMessage the result and retrieve the respective callback:
priv.returnResult = function (event) {
// here I get back the deferred (callback[0]) and callback function (callback[1])
var callback = priv.retrieveCallback(event.data.callback);
// problem = the callback stays resolved after the first function call
console.log(callback[0].state());
callback[0].resolve(event.data.result, callback[1]);
};
My problem is, although I'm creating a **new** $.Deferred every time a service is requested, my deferred object only runs the first time and then is set to resolved, preventing any further function calls to return a result.
I thought a new $.Deferred would generate a new deferred every time a service is requested, which I can resolve to run my callback function.
Question
What do I need to do to get this to work? Is it not, that new creates a new object, which I can resolve? Do I have to somehow delete/remove the previous resolved.deferred object in order to make it work?
Thanks!
Found a snippet extending jQuery deferred to handle "multiple deferred setters and resolvers":
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
deferred.resolve = doneList.fire;
// All done!
return deferred;
}
});