Basically I want this:
function do_ajax_calls(...){
var d = $.Deferred();
$.ajax(args).done(function(){
$.ajax(args).done(function(){
$.ajax(args).done(function(){
d.resolve();
});
});
})
return d.promise();
}
But the number of ajax calls depends on the arguments that I pass to the function, which is an array, so I can't use that code.
The function should return a promise that only resolves when the last ajax calls completes. So the function needs to be called like this:
do_ajax_calls(....).done(function(){
// here is the callback
})
Does anyone know how can I do this?
But the number of ajax calls depends on the arguments that I pass to the function, which is an array
If it's one ajax call per array item
function do_ajax_calls(args) {
return args.reduce(function(promise, item) {
return promise.then(function() {
return $.ajax(args); // should that be item?
});
}, Promise.resolve(true));
}
The Promise.resolve(true) is a "native" promise, i.e. not available in IE, but I'm sure jQuery has an equivalent
Here's a JSFiddle Demo
One of the reasons promises are a big deal is because they can be chained. You can use this to your advantage to iteratively chain additional requests onto the resolution of the previous one:
function do_ajax_calls() {
var dfd = $.Deferred();
var promise = dfd.promise();
var responses = [];
function chainRequest(url) {
promise = promise.then(function (response) {
responses.push(response);
return $.ajax(url, { method: 'POST' });
});
}
for (var i = 0, length = arguments.length; i < length; i++) {
chainRequest(arguments[i]);
}
dfd.resolve();
return promise.then(function (response) {
return responses.slice(1).concat(response);
});
}
The above code will return a promise ultimately resolving to an array of all of the responses. If any one of the requests fails, the promise will reject with the first failure.
JSFiddle
Here is it Demo
var counter = 1 ;
function multipleAjax(loop)
{
if(counter<loop)
{
$.ajax(
{
url: 'http://mouadhhsoumi.tk/echo.php',
success:function(data)
{
multipleAjax(loop);
$(".yo").append(data+"</br>");
counter++;
}
});
}
}
multipleAjax(5);
Try using $.when() , Function.prototype.apply() , $.map()
function do_ajax_calls(args) {
return $.when.apply($, $.map(args, function(request, i) {
return $.ajax(request) // `request` : item with `args` array
}))
}
do_ajax_calls
.then(function success() {
console.log(arguments)
}, function err() {
console.log("err", arguments)
});
Related
Take the following example:
_.each(arrayOfVals, function (val) {
$.when(getAjaxCall('foo',{val:val}))
.then(function (callResponse) {
_.each(callResponse, function (rep) {
console.log(rep);
});
});
I then want to call some code after the entirety of that code is complete. How can I do this?
You can pass multiple arguments to $.when and its deferred will resolve when they all complete. If you have an array, use .apply.
$.when.apply($, arrayOfVals.map(val => getAjaxCall('foo', {val}))
.then(responses => responses.map(resp => console.log(resp));
I ended up using a $.Deferred object to listen to when everything is done and called the resolve when I've gone through the entire list.
deferred = $.Deferred
i = 0;
_.each(arrayOfVals, function (val) {
$.when(getAjaxCall('foo',{val:val}))
.then(function (callResponse) {
_.each(callResponse, function (rep) {
i += 1;
console.log(rep);
if (i == callResponse.length) {
deferred.resolve();
}
});
});
deferred.done(function() {
console.log('everything done!');
}
I am currently working on a angular project, and I am kind of new to it.
I do not understand, why is .then() function not waiting for the promises?
I think it have to do something with that I only have one $q.defer() inside my getAllStats() function? When I try to console.log("testing: ", data); (on the bottom) it only logs out an empty array. Could someone help me please?
This is my code:
function getAllStats(dataArray, nameOfFile) {
var defer = $q.defer();
var promises = [];
for (index in dataArray) {
if (dataArray[index].indexOf('test') > -1 ) {
getStats(nameOfFile).then(function (data) {
promises.push();
});
}
}
function last() {
defer.resolve(promises);
}
$q.all(promises).then(last);
return defer.promise;
};
function getStats(nameOfFile) {
var defer = $q.defer();
$http.get(nameOfFile).success(function (data) {
defer.resolve(data);
});
return defer.promise;
};
getAllStats('test.txt').then(function(data) {
console.log("testing: ", data);
});
See the comments in this code:
function getAllStats(dataArray, nameOfFile) {
var promises = [];
// using `for (index in dataArray) {` is a bad idea unless
// dataArray is a non-array object
for (var index = 0; index < dataArray.length; index++) {
if (dataArray[index].indexOf('test') > -1 ) {
// Here is the trick, store the promise itself,
// don't try to subscribe to it here
promises.push(getStats(nameOfFile));
}
}
return $q.all(promises);
};
function getStats(nameOfFile) {
// http.get already returns a promise, see explicit promise creation antipattern
return $http.get(nameOfFile).then(function(r) { return r.data; });
};
getAllStats('test.txt').then(function(data) {
console.log("testing: ", data);
});
References:
Explicit promise creation antipattern
Why is for..in bad
Deprecation Notice
The $http legacy promise methods success and error
have been deprecated. Use the standard then method instead. If
$httpProvider.useLegacyPromiseExtensions is set to false then these
methods will throw $http/legacy error.
see: $http
In your example this block:
for (index in dataArray) {
if (dataArray[index].indexOf('test') > -1 ) {
getStats(nameOfFile).then(function (data) {
promises.push();
});
}
}
Takes dataArray, which is a string and runs through it char by char. Also, you are not setting nameOfFile. Change the call to:
getAllStats(['test.txt']).then(function(data) {
console.log("testing: ", data);
});
And then to make the push to promises be correct do like this:
promises.push(getStats(dataArray[index]));
Multiple issues wrong here:
You're passing an empty promises array to $q.all(). It has to be an array of promises at the time you pass it to $q.all().
You're creating promises when you can just return the ones you have
getAllStats() expects an array, but you're passing a string.
I'd suggest this overall cleanup of the code that fixes the above issues:
function getAllStats(dataArray) {
var promises = dataArray.filter(function(item) {
return item.indexOf('test') !== -1;
}).map(function(item) {
return $http.get(item);
});
return $q.all(promises);
};
getAllStats(['test.txt']).then(function(data) {
console.log("testing: ", data);
});
I'd also suggest you read about promise anti-patterns to teach yourself how to use the promises you already have and avoid creating new ones when new ones are not necessary.
P.S. I'm not sure what was the point of the nameOfFile argument since you don't want to be getStats() on the same file over and over again.
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 quite new to Angular, but I'm trying to find out some things.
I do have a method which returns a promise:
preloaderServiceObject.Load = function(referencePaths){
var deferred = $q.defer();
$(referencePaths).each(function(index, referencePath) {
var preloadedElement = document.createElement('img');
{
preloadedElement.onload = deferred.resolve;
preloadedElement.src = referencePath;
}
});
return deferred.promise;
}
This is all working fine and doesn't cause the problem.
However, I do have another method which should return a promise inside the completion call of the promise, like so:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
PreloaderService.Load(images);
});
}
The last line PreloaderService.Load(images); does return a promise as defined in the first function in this post.
But, now I want to call the method `OfficeUIRibbonControlServiceObject.Initialize', but how should i change this method so that I can wait for until the loading of the PreloaderService has been completed?
Just changing the method to return that promise will not work, because the returned object will be undefined (since I'm in the then method of the $http.
Kind regards,
Edit: As suggested by Rouby, using a promise:
The initialize function:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
var deferred = $q.defer();
PreloaderService.Load(images).then(function() {
deferred.resolve();
});
return deferred;
});
}
The InitializeService method:
function InitializeService(serviceInstance, configurationFile) {
serviceInstance.Initialize(configurationFile).then(function() {
console.log('This method has been called.');
});
}
The result of this is that I get: Error: serviceInstance.Initialize(...) is undefined
Create a new deferred in .Initialize that gets resolved when the second .Load finishes, you can then return this deferred as normal.
E.g.
PreloaderService.Load(images).then(function(){ newDeferred.resolve(); }, function(){ newDeferred.reject(); });
Better return the promise:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
return $http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
return PreloaderService.Load(images);
});
}
When you now call the OfficeUIRibbonControlServiceObject.Initialize function the result from PreloaderService.Load will be returned.
example:
OfficeUIRibbonControlServiceObject.Initialize(// myConfiguration //).then (
function success (response) {
console.log("promise success", response)
},
function fail (error) {
console.log("promise fail", error) // the result from PreloaderService.Load
}
);
In general: you can return values or promises in the .then function. When you return a promise. The resolve value of that promise will be returned after that promise is resolved
I have this class:
(function(){
"use strict";
var FileRead = function() {
this.init();
};
p.read = function(file) {
var fileReader = new FileReader();
var deferred = $.Deferred();
fileReader.onload = function(event) {
deferred.resolve(event.target.result);
};
fileReader.onerror = function() {
deferred.reject(this);
};
fileReader.readAsDataURL(file);
return deferred.promise();
};
lx.FileRead = FileRead;
}(window));
The class is called in a loop:
var self = this;
$.each(files, function(index, file){
self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
});
My question is, is there a way to call a method once the loop has completed and self.fileRead has returned it's deferred for everything in the loop?
I want it to call the method even if one or more of the deferred fails.
$.when lets you wrap up multiple promises into one. Other promise libraries have something similar. Build up an array of promises returned by fileRead.read and then pass that array to $.when and hook up then/done/fail/always methods to the promise returned by .when
// use map instead of each and put that inside a $.when call
$.when.apply(null, $.map(files, function(index, file){
// return the resulting promise
return self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
}).done(function() {
//now everything is done
})
var self = this;
var processFiles = function (data) {
var promises = [];
$.each(files, function (index, file) {
var def = data.fileRead.read(file);
promises.push(def);
});
return $.when.apply(undefined, promises).promise();
}
self.processFiles(self).done(function(results){
//do stuff
});
$.when says "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters. In this case, you have an array of promises;
I know this is closed but as the doc states for $.when: In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when immediately fires the failCallbacks for its master Deferred. (emphasis on immediately is mine)
If you want to complete all Deferreds even when one fails, I believe you need to come up with your own plugin along those lines below. The $.whenComplete function expects an array of functions that return a JQueryPromise.
var whenComplete = function (promiseFns) {
var me = this;
return $.Deferred(function (dfd) {
if (promiseFns.length === 0) {
dfd.resolve([]);
} else {
var numPromises = promiseFns.length;
var failed = false;
var args;
var resolves = [];
promiseFns.forEach(function (promiseFn) {
try {
promiseFn().fail(function () {
failed = true;
args = arguments;
}).done(function () {
resolves.push(arguments);
}).always(function () {
if (--numPromises === 0) {
if (failed) {
//Reject with the last error
dfd.reject.apply(me, args);
} else {
dfd.resolve(resolves);
}
}
});
} catch (e) {
var msg = 'Unexpected error processing promise. ' + e.message;
console.error('APP> ' + msg, promiseFn);
dfd.reject.call(me, msg, promiseFn);
}
});
}
}).promise();
};
To address the requirement, "to call the method even if one or more of the deferred fails" you ideally want an .allSettled() method but jQuery doesn't have that particular grain of sugar, so you have to do a DIY job :
You could find/write a $.allSettled() utility or achieve the same effect with a combination of .when() and .then() as follows :
var self = this;
$.when.apply(null, $.map(files, function(index, file) {
return self.fileRead.read(file).then(function(fileB64) {
self.fileShow(file, fileB64, fileTemplate);
return fileB64;//or similar
}, function() {
return $.when();//or similar
});
})).done(myMethod);
If it existed, $.allSettled() would do something similar internally.
Next, "in myMethod, how to distinguish the good responses from the errors?", but that's another question :)