Chaining multiple promises created through for loop - javascript

I have been studying promises through this link and I understood the idea of it
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
for (var i = 0; i < response.data['event-types'].length; i++) {
return $http.get('/api/security/' + response.data['event-types'][i]['key']);
}
})
.then(function(response) {
// response only returns one result of the many promises from the for loop
// do something with parentID;
});
However, my use case requires to loop through and send create more than 1 promises. I have tried to chain an as example above but only the only one of the promise created from the for loop was executed.
How can I continue chaining all of the promises while continue having access to the variable parentID?

You should use $q.all because it is integrated with the AngularJS digest cycle.
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
var promiseList = [];
for (var i = 0; i < response.data['event-types'].length; i++) {
var iPromise = $http.get('/api/security/' + response.data['event-types'][i]['key']);
promiseList.push(iPromise);
};
return $q.all(promiseList);
})
.then(function(responseList) {
console.log(responseList);
});
From the Docs:
all(promises);
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Parameters
An array or hash of promises.
Returns
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
--AngularJS $q Service API Reference -- $q.all

You can utilize Promise.all(), substitute Array.prototype.map() for for loop
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
return Promise.all(response.data['event-types'].map(function(_, i) {
return $http.get('/api/security/' + response.data['event-types'][i]['key'])
}))
})
.then(function(response) {
// response only returns one result of the many promises from the for loop
// do something with parentID;
})
.catch(function(err) {
console.log(err);
});

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
}

How to Resolve Promise for Another Promise?

The title is confusing, sorry.
I need to look at the contents of a promise for a subsequent promise.
See my previous thread for context: How to sequentially handle asynchronous results from API?
I have working code below, and I annotated my problem:
var promises = [];
while (count) {
var promise = rp(options);
promises.push(promise);
// BEFORE NEXT PROMISE, I NEED TO GET ID OF AN ELEMENT IN THIS PROMISE'S DATA
// AND THEN CHANGE 'OPTIONS'
}
Promise.all(promises).then(values => {
for (var i = 0; i < values; i++) {
for (var j = 0; j < values[i].length; j++) {
results.push(values[i][j].text);
}
}
return res.json(results);
}, function(reason) {
trace(reason);
return res.send('Error');
});
This is a perfect example of how promises can be chained, because the result of then is a new promise.
while (count) {
var promise = rp(options);
promises.push(promise.then(function(result) {
result.options = modify(result.options); // as needed
return result.options;
});
}
In this way, each promise given to Promise.all can be preprocessed.
If one promise depends upon another (e.g. it can't be executed until the prior one has finished and provided some data), then you need to chain your promises. There is no "reaching into a promise to get some data". If you want its result, you wait for it with .then().
rp(options).then(function(data) {
// only here is the data from the first promise available
// that you can then launch the next promise operation using it
});
If you're trying to do this sequence count times (which is what your code implies), then you can create a wrapper function and call itself from the completion of each promise until the count is reached.
function run(iterations) {
var count = 0;
var options = {...}; // set up first options
var results = []; // accumulate results here
function next() {
return rp(options).then(function(data) {
++count;
if (count < iterations) {
// add anything to the results array here
// modify options here for the next run
// do next iteration
return next();
} else {
// done with iterations
// return any accumulated results here to become
// the final resolved value of the original promise
}
});
}
return next();
}
// sample usage
run(10).then(function(results) {
// process results here
}, function(err) {
// process error here
});

Modify list of objects via WebService, how to get final Value List?

I get data from a web service, I need to send this data to another service to modify some values, so when I get the data there are more async calls to make. I don't know how to get the final values.
What I want to call:
SomeService.getSomething(id).then(function(something) {
SomeOtherService.addXToDatas(something.data).then(function(fixedData) {
$scope.data = fixedData;
});
});
What I have:
someOtherService.addXToDatas = function(datas) {
datas.forEach(function(value, index, array) {
getSomethingFromApi(value.something).then(function(bla) {
array[index] = bla;
});
});
}
someOtherService.addXToData = function(data) {
getSomethingFromApi(data.something).success(function(response) {
order.someMore = response.data;
});
}
}
Unfortunately I have no actual clue how to accomplish this in javascript.
For a single object I could use a callback, but for a list of objects?
Angular.js has build-in implementation of Q, so you could use promise combination Q.all. It takes array of promises and returns a promise that is fulfilled when all other promises are.
You can easily create array of promises with Your code using .map instead of .forEach:
someOtherService.addXToDatas = function(datas) {
return $q.all( datas.map(function(value, index, array) {
return getSomethingFromApi(value.something).then(function(bla) {
array[index] = bla;
});
})).then(function() {
return datas;
});
}
SomeOtherService.addXToDatas(something.data).then(function(fixedData) {
$scope.data = fixedData;
});
Explanation:
datas.map() creates an array of promises. API calls themselves will be executed asynchronously.
$q.all() returns a promise which is fulfilled when all promises passed to it from (1) are fulfilled.
Final then returns modified data array. It is called after all promises from (1) are fulfilled and all modifications of data are made.
If I understand you correctly you want an array filled with data from the async calls in addXToDatas, you can do that like this:
someOtherService.addXToDatas = function(datas) {
var promises = [];
datas.forEach(function(value, index, array) {
promises.push(
getSomethingFromApi(value.something)
);
});
return $q.all(promises);
}
And in your controller:
someOtherService.addXToDatas([data1, data2,data3])
.then(function(resultArray) {
console.log(resultArray);
});
$q.all(promises) will return an array (in order) with the values of the promises. More info about $q and $q.all here.

How does promise.all work?

I started diggin' in promises and found interesting Promise.all.
It is stated in MDN that
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved.
Which basically means that set promises resolve after and if all promises in argument list have been resolved. I tried to implement it. I made simply promise ajax call.
var get = function(url) {
return new Promise(function(resolve,reject) {
var xhtml=new XMLHttpRequest();
xhtml.open("GET",url);
xhtml.responseType = 'blob';
xhtml.onload = function() {
if(xhtml.status==200){
resolve(xhtml.response);
} else {
reject(Error("Error"+statusText));
}
}
xhtml.send();
});
}
get("one.jpg").then(function(response){
var blob = window.URL.createObjectURL(response);
var img = document.createElement("img");
console.log("Success"+response);
img.src = blob;
document.body.appendChild(img);
});
Which works fine. But after I tried to add Promise.all it threw an error.
Promise.all(get).then(function(response){alert("done")});
this as i said threw an error " Argument 1 of Promise.all can't be converted to a sequence."
So I assume i didn't get the meaning of promise.all.
How does it work?
Promise.all takes an array (or any iterable) of promises and fulfills when all of them fulfill or rejects when one of them rejects. I think it's easier to understand if we implement it and understand why we need it.
A common use case might be to wait for the window to load and for the server to return data in order to run some code:
// a function that returns a promise for when the document is ready.
function windowReady(){
return new Promise(function(resolve){
window.addEventListener('DOMContentLoaded', resolve);
});
}
// function that returns a promise for some data
function getData(){
return fetch("/").then(function(r){ return r.json() });
}
Now, we want both of them to execute at the same time and then get the result. There are two items here but there could have easily been 5 things to wait for, or 100. So we use Promise.all:
Promise.all([windowReady(), getData()]).then(function(results){
// results[1] is the data, it's all in an array.
});
Let's see how we can implement it:
function all(iterable){ // take an iterable
// `all` returns a promise.
return new Promise(function(resolve, reject){
let counter = 0; // start with 0 things to wait for
let results = [], i = 0;
for(let p of iterable){
let current = i;
counter++; // increase the counter
Promise.resolve(p).then(function(res){ // treat p as a promise, when it is ready:
results[i] = res; // keep the current result
if(counter === 0) resolve(results) // we're done
}, reject); // we reject on ANY error
i++; // progress counter for results array
}
});
}
Or, in even more ES6ness:
let all = iterable => new Promise((resolve, reject) => {
let arr = [...iterable], c = arr.length, results = [];
arr.map(Promise.resolve, Promise).
map((p, i) => p.then(v => {
r[i] = v;
if(--c === 0) resolve(r);
} , reject));
});
Your get function returns a Promise. You are just passing a reference to the get function. You have to pass an array of Promises
Promise.all([get("one.jpg")]).then(...);
TLDR:
Promise.all is a Javascript method that takes an iterable (e.g. Array) of promises as an argument and returns a single promise when all the promises in the iterable argument have been resolved (or when iterable argument contains no promises). It resolves with an array of the resolved values and rejects with a single value of the first rejected Promise.
Example:
var promise1 = Promise.resolve(5);
var promise2 = Math.random() > 0.5? 1 : Promise.reject(1); // either resolves or rejects
var promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('foo'), 1000);
});
Promise.all([promise1, promise2, promise3]).then((val) => {
console.log(val);
}).catch((val) => {
console.log(val);
});
In the above example 3 Promises are passed into the Promise.all function as an array. Promise 1 and 3 always resolve. Promise 2 either resolves or rejects based on the random Nr generator. This Promise.all method then returns a resolved or rejected Promise based on the random Nr generator.
Then the then() method and the catch() method can be called on this promise which is returned from Promise.all. The then() method gets an array of all the resolved values, [5, 1, 'foo'] in this case. The catch() method gets the value of the first rejected Promise, 1 in this example.
When to use:
This method is very usefull when you want to execute multiple async operations and need to something with the results after the async operations. When using Promise.all all promises can be processed at the same time while still getting to operate on all the incoming data.
For example, when we need to get information using multiple AJAX requests and combine the data to something useful. It is essential to wait for all the data to be available otherwise we would try to combine non existing data which would lead to problems.
The Promise.all(iterables) function returns a single Promise.Here we provide multiple Promises as argument. Promise.all(iterables) function returns promise only when all the promises (argument) have resolved.
It rejects when first promise argument reject.
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
// expected output: Array [3, 42, "foo"]
});
Syntax:
Promise.all(func1, func2 [,funcN])
Parameters:
Read more at - https://www.oodlestechnologies.com/blogs/An-Introduction-To-Promise.all-Function
Disclaimer: I work for oodles technologies

Categories