waiting for Promise in a Loop - javascript

Using AngularJs, when using a forEach Loop, the variable outside the loop still always 0:
to explain my problem, this is my code
var totald=0;
children.forEach(function (child) {
fetchdata(child['id']).then(function (resp) {
totald+= resp.total;
domaines.push({'id': child['id'], 'total': resp.total, 'details': resp.details});
});
});
After forEach, when I do console.log(totald), I get 0. but when I put console.log inside the forEach, the variable totald is incremented.
How I can resolve the problem and get the correct value of totald after the forEach finishied

You can map each promise as a list and await all of them using $q.all.
Something like this:
var totald = 0;
var promises = children.map(function (child) {
return fetchdata(child['id']).then(function(response){
return { id: child['id'], response: response };
});
});
$q.all(promises).then(function(results)){
results.forEach(function(result){
totald += result.response.total;
domaines.push({'id': result.id, 'total': result.response.total, 'details': result.response.details});
});
};

You should consider rewritting this code in a functional style; it will be much more readable:
const promises = children.map(async (child) => {
const response = await fetchdata(child['id']);
return {
id: child['id'],
response
};
});
const results = await Promise.all(promises);
const total = results.map(result => result.response.total)
.reduce((x, y) => x + y, 0);
const domains = results.map(result => ({
id: result.id,
total: result.response.total,
details: result.response.details
});
The most significant change is using map instead of forEach. There is never really a reason to use forEach because the for (... of ...) construct more clearly suggests side-effects. map is also more compact:
const ys = xs.map(x => x + 1);
vs...
const ys = [];
xs.forEach(x => {
ys.push(x + 1);
})
If you are concerned about browser-support for async-await then you can use Babel + Webpack.

You can use Promise.all:
var total = 0;
Promise.all(
children.map(function(c) { return fetchdata(child['id']); })
).then(function(datas) {
datas.forEach(function(data) {
total += data.total;
domaines.push({'id': child['id'], 'total': data.total, 'details': data.details});
});
});

Related

how can I get the final result from a array of async ?

get-video-duration is a npm module that get the duration of video.
const { getVideoDurationInSeconds } = require('get-video-duration')
// From a local path...
getVideoDurationInSeconds('video.mov').then((duration) => {
console.log(duration)
})
I want to use this module to get the total duration of all videos from an Array of video pathes.
function getTotals(video_Array) {
let total_duration = 0;
video_Array.forEach(video => {
getVideoDurationInSeconds(video).then(duration => {
total_duration += duration;
})
})
}
The thing is getVideoDurationInSeconds is Asynchronous, I can't just simply return the result.
function getTotals(video_Array) {
let total_duration = 0;
video_Array.forEach(video => {
getVideoDurationInSeconds(video).then(duration => {
total_duration += duration;
})
})
return total_duration;
}
How can I get the final result? Thank you in advance!
create a function which returns a promise
and then use it to calculate total duration
function getTotals(video_Array) {
let video_ArrayPromises=video_Array.map(video=>
getVideoDurationInSeconds(video));
return Promise.all([video_ArrayPromises]).then((values) => {
//Calculate TotalDuration
return duratons.reduce((accumulator, currentValue) => accumulator + currentValue);
});
}
getTotals(['movie1.mov','movie2.mov']).then(totalDuration => {
//use total duration
});
Create an array of getVideoDurationInSeconds promises with map, then reduce over the values returned by Promise.all to get your final total.
Additional documentation
async/await
// Mock function that returns a promise.
// When it's resolved it will return a random number
// muliplied by the element passed in through the function arguments
function getVideoDurationInSeconds(el) {
const rnd = Math.floor(Math.random() * (10 - 1) + 1);
return new Promise((res, rej) => {
setTimeout(() => {
console.log(el * rnd);
res(el * rnd);
}, 1000);
});
}
async function getTotals(videoArray) {
// `map` over the elements of the video array
// and create a new array of promises
const promises = videoArray.map(el => getVideoDurationInSeconds(el));
// Wait until all the promises have resolved
const data = await Promise.all(promises);
// Then return the sum of each total in the data array
return data.reduce((acc, c) => acc += c, 0);
}
(async function main() {
console.log(`Total: ${await getTotals([1, 2, 3, 4])}`);
}());
Return an array of requests and use reduce to get the total time.
// Array
function getTotalTime() {
return videoList.map(async (video) => await getVideoDurationInSeconds(video));
}
// Just invoke whereever you want..
await Promise.all(getTotalTime()).then(result => {
let totalTime = result.reduce((acc, cv) => acc + cv, 0); // 0 = default value
})

JS nested promises in for loop

I have something like:
let promises = [];
fetch("list.of.names").then(names => {
for n of names {
promises.push(fetch("names/"+n));
}
Promise.all(promises).then(all => {
for item of all {
//item and name `n` are needed:
element.innerText += n + ": " item.info;
}
})
})
And the thing is I need both in the end, but obviously n is just the last value, because the for loop already finished. Any idea how I can do that nicely? Is there a way to append elements to a promise?
I guess you can make fetch(name) resolve into something like [result, name]:
fetch("list.of.names").then(names => {
let promises = [];
for (let name of names) {
promises.push(
fetch("names/" + name).then(item => [item, name])
);
}
return Promise.all(promises);
}).then(all => {
for (let [item, name] of all) {
// do stuff
}
})
This is just an improvement (imo.) of #basickarl's code
(async() => {
// this needs to be done first:
const names = await fetch('list.of.names');
// this part, we'd like to happen in paralell:
const promises = names.map(async(name) => [name, await fetch(`names/${name}`)]);
// there are no guarantees that the requests will resolve in sequence; nor do we care.
// but this, we'd want to be executed in sequence:
for await (const [name, item] of promises) {
element.innerText += name + ': ' + item.info;
}
})();
Try this:
(async () => {
const names = await fetch('list.of.names');
const namesAndItemsPromises = names.map(async (name) => {
const item = await fetch(`names/${name}`);
element.innerText += name + ': ' + item.info;
return [name, item];
});
const namesAndItems = Promise.all(namesAndItemsPromises);
})();
My advice would be to try and get away from "then" as much as possible, it causes so much confusion.

Javascript map, reduce not working when implemented within object method

Based on the answer from this question I implemented the map reduce code within an object method.
this.displayValueGraph = async () => {
let scaleData = [];
this.positions.forEach(async (pos, i) => {
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
});
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};
The code by itself works fine. I have taken the input data (above scaleData) and run it through the map reduce function and the output is as expected. But if I include it as part of this method it does nothing. It doesn't throw any errors, it simply returns an empty array.
I have tried adding an empty array as an "initial value", but it doesn't help.
The root cause of the problem appears to have been the first forEach loop, where I included an await. I replaced the forEach with for in and it solved the problem.
this.displayValueGraph = async () => {
let scaleData = [];
for (const i in this.positions) {
const pos = this.positions[i];
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
}
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};

Axios.get().then() in a for loop

How would I go about running Axios in a for loop, each with a corresponding .then() function. Then after the for loop ends, run another function.
Example:
const array = ['asdf', 'foo', 'bar'];
let users = [];
for (i = 0; i < array.length; i++) {
axios.get('/user/' + array[i].id).then(response => {
// do something with response
users.push(response);
});
}
console.log(users);
const array = [{ id: 'asdf'}, { id: 'foo' }, { id: 'bar' }]; // changed the input array a bit so that the `array[i].id` would actually work - obviously the asker's true array is more than some contrived strings
let users = [];
let promises = [];
for (i = 0; i < array.length; i++) {
promises.push(
axios.get('/user/' + array[i].id).then(response => {
// do something with response
users.push(response);
})
)
}
Promise.all(promises).then(() => console.log(users));
The .then() method of a Promise itself returns a Promise; so you can collect those and await all of them with Promise.all().
Note that even if you're doing this within an async function, you don't want to await inside the for-loop, because then each request will wait for the previous one to finish before it even starts, and presumably you want to run these requests in parallel.
Depending on your use case, a concise async / await function might look like this:
async function getMultiple(...objectsToGet) {
let users = [];
await Promise.all(objectsToGet.map(obj =>
axios.get('/user/' + obj.id).then(response => {
// do something with response
users.push(response);
})
));
return users;
}
// some other async context
console.log(await getMultiple({ id: 'asdf'}, { id: 'foo' }, { id: 'bar' }));
If you are using a more recent version of javascript with async/await support, you can do the following:
const array = ['asdf', 'foo', 'bar'];
let users = [];
for (const id in array) {
const response = await axios('/user/' + id);
users.push(response);
}
console.log(users);
You should collect all the promises inside an array and use promise.all in the following manner -
const array = ['asdf', 'foo', 'bar'];
let promises = [];
for (i = 0; i < array.length; i++) {
promises.push(axios.get('/user/' + array[i].id))
}
Promise.all(promises)
.then(responses => console.log(responses));

How can I (efficiently) chain my promise that returns an array with value rather than promiess?

I have a promise chain that finds an array. I want the array to return some results (where in the future I can set it to a state variable). However, I am only getting promises. I am wondering what I am doing wrong here.
For now, I am console logging the items in my array to see if it returns the value, rather than the Promise.
addIPFSItem = () => {
var finalItems = []
var searchAddress = "0x9Cf0dc46F259542A966032c01DD30B8D1c310e05";
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
this.state.web3.eth.getAccounts((error, accounts) => {
simpleStorage.deployed().then((instance) => {
this.simpleStorageInstance = instance
return this.simpleStorageInstance.getLength(searchAddress);
}).then((accountLength) => {
var movieItems = []
var i;
var indexCounter = 0;
//WITHIN THIS LOOP extend the chain to add to movies
for (i = 0; i < accountLength; i++) {
var p = this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress }).then((hashVal) => {
return hashVal;
}).then((hashVal)=>{
var ipfsPrefix = "https://ipfs.io/ipfs/";
var ipfsURL = ipfsPrefix + hashVal;
return ipfsURL
})
var k = this.simpleStorageInstance.getTitle(searchAddress, i, { from: searchAddress }).then((title) => {
return title;
})
var l = this.simpleStorageInstance.getContents(searchAddress, i, { from: searchAddress }).then((contents) => {
return contents;
})
movieItems.push({id: i, poster_src: p, title: k, overview: l})
indexCounter++
console.log('counter: ', i, p, k, l)
}
//return items
return movieItems
}).then((array) =>{
var i
for(i=0; i<array.length; i++){
console.log('Array item: ', array[i])
}
return finalItems
})
})
}
I don't know if I need to chain the var p, var k, var l when adding it to movieItems array. Supposedly, after I push to movieItems, I can then try and get each value within the array as I have already retrieved the values right?
You are trying to cheat the promises here!
When you run
var l = this.simpleStorageInstance.getContents(searchAddress, i, { from: searchAddress }).then((contents) => {
console.log('Retrieved contents');
return contents;
});
movieItems.push({id: i, poster_src: p, title: k, overview: l});
console.log('Pushed!')
the javascript engine is going say "I'll put this Promise into l directly and continue to movieItems.push". If you run this, you'll see the logs appear as
Pushed!
Retrieved contents
There is a lot to learn about promises, so I can't explain everything in this one post. A quick fix for now, assuming you have an environment or build tool that supports it, is using async/await. That way you can make javascript "just wait" until your promises are resolved.
first you change
}).then((accountLength) => { to }).then(async (accountLength) => {
which tells javascript you are going to use await and work with Promises inside the function. Then you can, instead of using .then, use await like this:
// Here I use `await` instead of `.then` to make the promise return "normally"
var hashVal = await this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress });
// I do the transformations you did inside `.then(() => { ... })` "just" after the function call
var ipfsPrefix = "https://ipfs.io/ipfs/";
var ipfsURL = ipfsPrefix + hashVal;
var p = ipfsURL;
// Again, using `await` instead of `.then`
var k = await this.simpleStorageInstance.getTitle(searchAddress, i, { from: searchAddress })
var l = await this.simpleStorageInstance.getContents(searchAddress, i, { from: searchAddress });
This would make your function actually return an array of values instead of an array with promises inside.
Hope this helps! :)
That's because the value of variables p, k, and l are Promises. And they will always be promises, because that's what this.simpleStorageInstance.getBook() et al return. Adding a .then() to them doesn't make them synchronously represent their values, they are still Promises. You need to compose Promises cleverly in order to ensure the values you want are available at the time and place you want them - I suggest you do more reading on how they work.
A straightforward fix might be to simply use await. It basically pauses execution until the asynchronous Promise finishes, and will ensure that p, k, and l represent the values that you expect of them. MDN documentation.
addIPFSItem = () => {
var finalItems = []
var searchAddress = "0x9Cf0dc46F259542A966032c01DD30B8D1c310e05";
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
this.state.web3.eth.getAccounts((error, accounts) => {
simpleStorage.deployed().then((instance) => {
this.simpleStorageInstance = instance
return this.simpleStorageInstance.getLength(searchAddress);
}).then((accountLength) => {
var movieItems = []
var i;
var indexCounter = 0;
//WITHIN THIS LOOP extend the chain to add to movies
for (i = 0; i < accountLength; i++) {
let p = await this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress }).then((hashVal) => {
return hashVal;
}).then((hashVal) => {
var ipfsPrefix = "https://ipfs.io/ipfs/";
var ipfsURL = ipfsPrefix + hashVal;
return ipfsURL
});
let k = await this.simpleStorageInstance.getTitle(searchAddress, i, { from: searchAddress });
let l = await this.simpleStorageInstance.getContents(searchAddress, i, { from: searchAddress });
movieItems.push({ id: i, poster_src: p, title: k, overview: l })
indexCounter++
console.log('counter: ', i, p, k, l)
}
//return items
return movieItems
}).then((array) => {
var i
for (i = 0; i < array.length; i++) {
console.log('Array item: ', array[i])
}
return finalItems
})
})
}
P.S. If you're using const you should be using let instead of var. There is basically no reason whatsoever to use var in ES6.

Categories

Resources