how to resolve all promises in a for loop? - javascript

Yet another question about promises. I have this scenario:
Service.prototype.parse = function (data) {
var deferred = $.Deferred();
var arr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
$.when(details).then(function (data) {
arr.push(data);
deferred.resolve(arr);
});
}
return deferred.promise;
};
Somewhere else in the code:
...
$.when(parse()).then(function (resp) {
//...
});
The promises get resolved at some point but initially resp has a length of 1.
How to wait for parse() to resolve everything and return a array?

No need for a deferred anti pattern (explicit construction) or for explicit array construction. Your code can be simplified to:
Service.prototype.parse = function (data) {
return $.when.apply($, data.map(function(x){
return new Details();
}).then(function(){ return arguments; });//.then(Array.of); // instead, if want array
};
Some general advice:
You're ignoring the item you're iterating in the data, I assume you don't do that in your real code but just making sure.
It's generally not the best idea to perform async IO in a constructor. Please consider separating construction and initialization.
You should consider using native promises (or a library) or using jQuery 3.0.0 which supports non problematic promises.

Please try this:
Service.prototype.parse = function(data) {
var detailsArr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
detailsArr.push(details);
}
return $.when.apply($, detailsArr).then(function(){
return Array.prototype.slice.call(arguments);
});
};
Here we put all Details into an array, use $.when.apply($, arr) to wait for all Details get resolved. When it's done, each Details' return data will be passed to callback function as one parameter, so the call back function will receive total data.length number of parameters. Then we use Array.prototype.slice.call to convert all parameters to an array and return the result.
For your reference:
What does $.when.apply($, someArray) do?
How can I convert the "arguments" object to an array in JavaScript?

Related

Promise in while loop javascript

I'm trying to generalize a script that encrypts forms using OpenPGP libraries.
I got some troubles with the client-side code (Javascript) :
var working = formID.elements[0];
var counter = 0;
while (working) {
encrypt(working.value).then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
});
console.log("assuming processed");
var counter = counter + 1;
var working = formID.elements[counter];
}
The following code should take each form element and encrypt its value. However, the while loop doesn't wait for the asynchronous encrypt() function to be resolved.
I think i need to use promises in this case, but i have no idea how and the few tutorials didn't work in a while loop.
Help ?
Probably can be used list of jQuery deferreds, something like this:
var deferreds = [];
$.each(formID.elements, function(key, working){
var deferred = $.Deferred();
deferreds.push(deferred);
encrypt(working.value).then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
deferred.resolve();
});
});
$.when.apply( $, deferreds ).done(function(){
console.log( 'after all encryptions!' );
});
Of course, can be used native Promise object instead $.Deferred, however I think $.Deferred is more cross-browser way.
UPD2:
Improved answer based on native Promise and Promise.resolve() (thanks to #Bergi). For the case when encrypt() returns correct promise, method Promise.resolve() can be skipped.
var promises = [];
$.each(formID.elements, function(key, working){
var promise = Promise.resolve(encrypt(working.value))
.then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
});
promises.push(promise);
});
Promise.all(promises).then(function(){
console.log( 'after all encryptions!' );
});
var iterator = [];
for (var counter = 0; counter < formID.elements.length; counter++) {
var working = formID.elements[counter];
iterator.push(encrypt(working.value));
}
Promise.all(iterator)
.then(fumction(data){
//Here you have all data
})
You can synchronize your operation like this way. By collecting all asynchronus value references and point to them when they have data.
In case your data is dependent.
function myfunction(previousValue){
if(breaking Condition){
return Promise.resolve();
}
return encrypt(working.value).then(function(encrypted_msg) {
working.value = encrypted_msg;
return myfunction(working);
});
}

Using closure with a promise in AngularJS

I don't have a lot of experience with JavaScript closures nor AngularJS promises. So, here is my scenario
Goal:
I need to make $http requests calls within a for loop
(Obvious) problem
Even though the loop is done, my variables still have not been updated
Current implementation
function getColumns(fieldParameters)
{
return $http.get("api/fields", { params: fieldParameters });
}
for(var i = 0; i < $scope.model.Fields.length; i++)
{
var current = $scope.model.Fields[i];
(function(current){
fieldParameters.uid = $scope.model.Uid;
fieldParameters.type = "Columns";
fieldParameters.tableId = current.Value.Uid;
var promise = getColumns(fieldParameters);
promise.then(function(response){
current.Value.Columns = response.data;
}, error);
})(current);
}
//at this point current.Value.Columns should be filled with the response. However
//it's still empty
What can I do to achieve this?
Thanks
If I understand your question correctly, you have a list of fields that you need to do some work on. Then when all of that async work is done, you want to continue. So using the $q.all() should do the trick. It will resolve when all of the list of promises handed to it resolve. So it's essentially like "wait until all of this stuff finishes, then do this"
You could try something like this:
var promises = [];
for(var i=0; i< $scope.model.Fields.length; i++) {
var current = $scope.model.Fields[i];
promises.push(getColumns(fieldParameters).then(function(response) {
current.Value.Columns = response.data;
}));
}
return $q.all(promises).then(function() {
// This is when all of your promises are completed.
// So check your $scope.model.Fields here.
});
EDIT:
Try this since you are not seeing the right item updated. Update your getColumns method to accept the field, the send the field in the getColumns call:
function getColumns(fieldParameters, field)
{
return $http.get("api/fields", { params: fieldParameters}).then(function(response) {
field.Value.Columns = response.data;
});
}
...
promises.push(getColumns(fieldParameters, $scope.model.Fields[i])...
var promises = [];
for(var i = 0; i < $scope.model.Fields.length; i++)
{
var current = $scope.model.Fields[i];
promises.push(function(current){
//blahblah
return promise
});
}
$q.all(promises).then(function(){
/// everything has finished all variables updated
});

Using data from one async API call to another

I have a question in regards to asynchronous data calls in AngularJS.
The issue is that I will be recieving multiple objects in an array from one API call, and I will have to extend those objects with data from a different API call.
I was thinking of having nested async calls, but my logic falls a bit short in terms of how this would work with the $q service. I have a service which will return the object which has been extended with the second call so I can use this in a controller for display in a view.
The first API call returns some parameters which I need for the second API call in order to get the relevant data for which I will return back to the controller.
I will have to loop inside the first call so I can then run the second API call inside of that, but how am I going to return this back to my controller? I cannot resolve when the first loop has been run, because well, it explains itself.
What is the go-to solution for something like this?
Edit, my issue in pseudo-javascript:
returnListOfStuff().then(function (data) {
var result = data.Result;
for (var i = 0; i < result.length; i++) {
var dataForListItem = null;
returnDataForListItem(result[i].ID).then(function (data) {
dataForListItem = data;
});
for (prop in dataForListItem[0]) {
result[i].prop = dataForListItem[0][prop];
}
}
return result;
});
As is apparent, this won't work, considering it will only fetch the results from the first call returnListOfStuff(), because what is happening inside the for loop is not yet resolved. I can't really figure out how to do this with $q.all(), because I don't have the parameters from the returnListOfStuff function yet
I can't really figure out how to do this with $q.all(), because I don't have the parameters from the returnListOfStuff function yet
You can just do it in the then callback where you have them.
You can return the promise that $q.all yields from the callback to get a promise for the propped up result.
returnListOfStuff().then(function (data) {
var result = data.Result;
return $q.all(result.map(function(resultItem) {
return returnDataForListItem(resultItem.ID).then(function (data) {
for (prop in data[0]) {
resultItem[prop] = data[0][prop];
}
return resultItem;
});
}));
});
Try use $q.all() for this:
var loadQ = [];
var dataForListItem = null;
for (var i = 0; i < result.length; i++) {
loadQ.push(returnDataForListItem(result[i].ID));
}
$q.all(loadQ).then(function(values){
dataForListItem = values;//or values[0], I dnt known Your data structure;
});
If You will have problem with i value, try use:
for (var i = 0; i < result.length; i++) {
(function(e) {
loadQ.push(returnDataForListItem(result[e].ID));
})(i);
}

return after a series of functions in javascript

I have tried to do this with q as well as async, but haven't been able to seem to make it work. After trying those I tried my own way. I didn't think this would work, but I thought I would give it a try. I am confused since there is a callback within a callback in a sense. Here is the function I am wanting to do:
var getPrice = function(theData) {
var wep = theData.weapon;
var completed = 0;
for (i = 0; i < theData.skins.length; i++) {
var currSkin = theData.skins[i];
theData.skinData[currSkin] = {};
for (k = 0; k < wears.length; k++) {
csgomarket.getSinglePrice(wep, currSkin, wears[k], false,
function(err, data) {
completed++;
if (!err) {
theData.skinData[data.skin][data.wear] = data;
}
if (completed === theData.skins.length*wears.length) {
return theData;
}
})
}
}
}
I know these kinds of issues are common in javascript as I have ran into them before, but not sure how to solve this one. I am wanting to fill my object with all the data returned by the method:
csgomarket.getSinglePrice(wep, currSkin, wears[k], false,
function(err, data) { });
Since each call to getSinglePrice() sends off a GET request it takes some time for the responses to come back. Any suggestions or help would be greatly appreciated!
First csgomarket.getSinglePrice() needs to be promisified. Here's an adapter function that calls csgomarket.getSinglePrice() and returns a Q promise.
function getSinglePriceAsync(wep, skin, wear, stattrak) {
return Q.Promise(function(resolve, reject) { // may be `Q.promise(...)` (lower case P) depending on Q version.
csgomarket.getSinglePrice(wep, skin, wear, stattrak, function(err, result) {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
}
Now, you want getPrice() to return a promise that settles when all the individual getSinglePriceAsync() promises settle, which is trivial :
var getPrice = function(theData) {
var promises = [];//array in which to accumulate promises
theData.skins.forEach(function(s) {
theData.skinData[s] = {};
wears.forEach(function(w) {
promises.push(getSinglePriceAsync(theData.weapon, s, w, false).then(function(data) {
theData.skinData[data.skin][data.wear] = data;
}));
});
});
//return a single promise that will settle when all the individual promises settle.
return Q.allSettled(promises).then(function() {
return theData;
});
}
However, theData.skinData[data.skin][data.wear] will simplify slightly to theData.skinData[s][w] :
var getPrice = function(theData) {
var promises = [];//array in which to accumulate promises
theData.skins.forEach(function(s) {
theData.skinData[s] = {}; //
wears.forEach(function(w) {
promises.push(getSinglePriceAsync(theData.weapon, s, w, false).then(function(data) {
theData.skinData[s][w] = data;
}));
});
});
//return a single promise that will settle when all the individual `promises` settle.
return Q.allSettled(promises).then(function() {
return theData;
});
}
This simplification would work because the outer forEach(function() {...}) causes s to be trapped in a closure.
As getPrice() now returns a promise, it must be used as follows :
getPrice(myData).then(function(data) {
// use `data` here.
}).catch(function(e) {
//something went wrong!
console.log(e);
});
Your way to do is very complex.
I think the best way is to do 1 request for all prices. Now, for each price you do a request.
If you have a list (array) with data that's need for the request, the return value should be a list with the prices.
If approach above is not possible, you can read more about batching http requests: http://jonsamwell.com/batching-http-requests-in-angular/
Need some clarification - Are you trying to run this on the Client side? Looks like this is running inside a nodejs program on server side. If so, wouldn't you rather push this logic to the client side and handle with Ajax. I believe the browser is better equipped to handle multiple http request-responses.
Since you didn't post much info about your csgoMarket.getSinglePrice function I wrote one that uses returns a promise. This will then allow you to use Q.all which you should read up on as it would really help in your situation.
I've created an inner and outer loop arrays to hold our promises. This code is completely untested since you didn't put up a fiddle.
var getPrice = function(theData) {
var wep = theData.weapon;
var completed = 0;
var promises_outer = [] //array to hold the arrays of our promises
for (var i = 0; i < theData.skins.length; i++) {
var currSkin = theData.skins[i];
theData.skinData[currSkin] = {};
var promises_inner = [] // an array to hold our promises
for (var k = 0; k < wears.length; k++) { //wears.length is referenced to below but not decalared anywhere in the function. It's either global or this function sits somewhere where it has access to it
promises_inner.push(csgomarket.getSinglePrice(wep, currSkin, wears[k], false))
}
promises_outer.push(promises_inner)
}
promises_outer.forEach(function(el, index){
var currSkin = theData.skins[index]
theData.skinData[currSkin] = {}
Q.all(el).then(function(data){ //data is an array of results in the order you made the calls
if(data){
theData.skinData[data.skin][data.wear] = data
}
})
})
}
var csgomarket = {}
csgomarket.getSinglePrice = function(wep, currSkin, wears, someBoolean){
return Q.promise(function (resolve, reject){
//do your request or whatever you do
var result = true
var data = {
skin : "cool one",
wear : "obviously"
}
var error = new Error('some error that would be generated')
if(result){
resolve(data)
} else {
reject(error)
}
})
}

Multiple jQuery Ajax requests and response handling [duplicate]

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

Categories