Looping a javascript promise to append to an array - javascript

Good day, below I have
var request = require('request');
getFood = function(lst){
return new Promise(function(resolve, reject){
//console.log(lst)
request('https://api.jamesoff.net/recipe', function (error, response, recipe) {
lst.push(JSON.parse(recipe))
resolve(lst);
})
});
}
getFood([]).then(function(data){
for(var i = 0; i < 3; i++){
getFood(data)
}
return(data);
}).then(function(data){
console.log(data)
})
What I am aiming to accomplish is by using a promise chain place three recipies into an array using a loop. I only get one item and I know why this occurs, however I can't wrap my head around the procedure to accomplish my goal. Could I please get an explanation with code?

The problem is your getFood call in a loop. If you are looping through promises the best way to do it is with Promise.all()
An example:
var promiseArray = [];
for(var i = 0; i < 3; i++){
promiseArray.push(getFood(data))
}
Promise.all(promiseArray).then(function(values) {
// values is array of resolve values in the order that promises were pushed into the array
});
This resolves the promises in the order in which they were called and should help with your issues.

Related

Running async function from a loop

I'm not looking for any solution in any particular language, I just want to understand what are the good practices in the area of running async tasks from a loop.
This is what I have thought of so far:
var callAcc = 0;
var resultArr = [];
for (var k = 0; k < 20; k++) {
callAcc ++;
asyncFunction("variable")
.then(function (result) {
resultArr.push(result);
if (callAcc === resultArr.length) {
// do something with the resultArr
}
})
.catch(function (err) {
console.warn(err);
});
}
At this point, I'm just using a sync variable that will only let me proceed once I have all of the async tasks complete. However, this feels hacky and I was wondering if there was some kind of design pattern to execute aync tasks from a loop
EDIT:
Based on the proposed solutions, this is what i ended up using
var promises = [];
for (var i = 0; i < 20; i ++) {
promises.push(asyncFunction("param"));
}
Promise.all(promises)
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.warn(err);
});
Thank you everyone!
Instead of running your async tasks within a loop, you can abstract away the task of waiting on all your promises by using the Promise.all function, which will take an iterable of your promises and create a new promise which will return an array with all of their results in the same order as the original list of promises as soon as all of them resolve, or fail if any of them fails. Most languages which have Promises have a function like that, and if there isn't, it shouldn't be too hard to define.
If you need all of your promises to run even if any of them fails, you can write a function that does that too, you'd just need to define what to do with the failed Promises (discard them, return the errors somehow...).
Anyway, the gist of it is that the best way of dealing with orchestration of multiple Promises is to define a function which will take all the Promises you need to deal with and return a new Promise which will handle the orchestration.
Something like:
orchestratePromises(promiseList) {
return new Promise((resolve, reject) => {
// set up
for (let promise of promiseList) {
// define how to handle your promises when they resolve
// asynchronously resolve or reject
}
}
}

How to create a loop of promises

so i have a promise that collects data from a server but only collects 50 responses at a time. i have 250 responses to collect.
i could just concate promises together like below
new Promise((resolve, reject) => {
resolve(getResults.get())
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
}).then((results) => {
totalResults.concat(results)
return getResults.get()
})
In this instance i only need 250 results so this seems a managable solution but is there a way of concating promises in a loop. so i run a loop 5 times and each time run the next promise.
Sorry i am new to promises and if this were callbacks this is what i would do.
If you want to loop and serialise the promises, not executing any other get calls once one fails, then try this loop:
async function getAllResults() { // returns a promise for 250 results
let totalResults = [];
try {
for (let i = 0; i < 5; i++) {
totalResults.push(...await getResults.get());
}
} catch(e) {};
return totalResults;
}
This uses the EcmaScript2017 async and await syntax. When not available, chain the promises with then:
function getAllResults() {
let totalResults = [];
let prom = Promise.resolve([]);
for (let i = 0; i < 5; i++) {
prom = prom.then(results => {
totalResults = totalResults.concat(results);
return getResults.get();
});
}
return prom.then(results => totalResults.concat(results));
}
Note that you should avoid the promise construction anti-pattern. It is not necessary to use new Promise here.
Also consider adding a .catch() call on the promise returned by the above function, to deal with error conditions.
Finally, be aware that concat does not modify the array you call it on. It returns the concatenated array, so you need to assign that return value. In your code you don't assign the return value, so the call has no effect.
See also JavaScript ES6 promise for loop.
Probably you just need Promise.all method.
For every request you should create a promise and put it in an array, then you wrap everything in all method and you're done.
Example (assuming that getResults.get returns a promise):
let promiseChain = [];
for(let i = 0; i <5; i++){
promiseChain.push(getResults.get());
}
Promise.all(promiseChain)
.then(callback)
You can read more about this method here:
Promise.all at MDN
EDIT
You can access data returned by the promises this way:
function callback(data){
doSomething(data[0]) //data from the first promise in the chain
...
doEventuallySomethingElse(data[4]) //data from the last promise
}

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

Promises for loop angular confused

I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});

how to resolve all promises in a for loop?

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?

Categories