modify array after promise.all in foreach loop - javascript

I have an array like this
let result = [{id:1,name:'test',dealValue:'ds',dealType:2},{id:2,name:'test1',dealValue:'ds',dealType:4}];
I am looping the above array to call another function which is also a promise after each iteration I need to add the value as a new item in the current array.
let temp = [];
result.forEach((element, index) => {
//build the promise
temp.push(getAmount(element.dealValue, element.dealType));
});
//execute array of promise
let r = await Promise.all(temp);
//add new key value pair
result.forEach((element, index) => {
element.IDGfee = r[index];
});
This works fine, however, I am running two foreach loops to achieve my desired result ,is there any better way to do this??

You could use .map instead, and assign back to element inside a .then chained onto the getAmount call:
await Promise.all(
result.map((element) => (
getAmount(element.dealValue, element.dealType)
.then((result) => {
element.IDGfee = result;
})
))
);
(Though, as comment notes, this will not wait for every response to come back before assigning to each element - your current code will throw before assigning if any request throws an error, whereas this may well throw after some properties have been assigned to.)

Related

aysnc map is returning empty array

the following code block is returning an empty array but if I use for loop instead it works fine
let allMenus = [];
foodMenu = foodMenu.map(async menu => allMenus.push(await handleMenu(menu, DBName)))
console.log(foodMenu); // returns empty array
this return the data perfectly but I want to use map
let allMenus = [];
for (const menu in foodMenu) {
allMenus.push(await handleMenu(foodMenu[menu], DBName)); // this returns the data
}
A few things:
Firstly, Array#map expects a return value because it generates a new array. While your code works, it is not the intent of the method.
Using await in an array enumerator callback (Array#map, in yours case) will defer the execution, but it does not pause between callbacks. That means the code will be run, but it will not be resolved in sequence in the way you are expecting.
Do this:
let foodMenu = foodMenu.map(async menu => {
const newMenuItem = await handleMenu(menu, DBName)
console.log(newMenuItem)
allMenus.push(menu)
})
You will find that your return value, the empty array, will be printed first, and then your new menus printed afterwards. It is out of order
To resolve this, you either need to
Make it a loop, where await that will pause in the way you expect or
Map the promises into an array and wait for them all to finish, using Promise.all
let foodMenu = await Promise.all(foodMenu.map(menu => handleMenu(menu, DBName)))

Problems with async / await - How to wait for requests

I have an array of objects containing metadata for videos. I need to iterate through it and for each object I need to make an API call passing its filename to get a streamingLink and assign it to the object's streamingLink. My problem is by the time I am returning this array, the array is undefined. How do I tell the code to wait until streamingLink has been assigned?
Here's kind of what my code looks like:
// get array from database
const items = await client.db('database').collection('collection').find({}).toArray();
// for each object get and replace streamingLink
let items_withLinks = items.map((item) => {
getStreamingLink(item.filename) // API call to get link
.then(response => {
item.streamingLink = response.result.link;
return item;
})
});
console.log(items_withLinks); // undefined
I have tried using await in different places and wrapping this in an async function but I can't figure out the right way to do it.
You can loop over your array and create a promise for each item. Then you can call Promise.all.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
The fundamental problem is that the function you are using in the .map doesn't return anything. (So you will actually technically get an array of undefined values, rather than undefined itself - but that obviously is no good.)
You need to return the promise and then use Promise.all to get the array of results.
// get array from database
const items = await client.db('database').collection('collection').find({}).toArray();
// for each object get and replace streamingLink
let items_withLinks = items.map((item) => {
// return is the only thing I've added here!
return getStreamingLink(item.filename) // API call to get link
.then(response => {
item.streamingLink = response.result.link;
return item;
})
});
const result = await Promise.all(items_withLinks);
console.log(result);

Extracting JSON data from fetch promises using Array.reduce()

I'm writing a then() statement for extracting the json data from an array of responses from fetch(). In the code below queries is an array of promises returned by a series of calls to fetch(). I'm using async/await for the response because otherwise the promises would be returned without resolving (I found a solution in this question).
My first attempt worked properly, when I push into jsonified I obtain an array with the promises as elements:
return Promise.all(queries)
.then(async(responses)=> {
let jsonified = [];
for (let res of responses){
jsonified.push(await(res.json()));
}
return jsonified;
}.then(data=> ...
But when I went for refactoring and tried to use Array.reduce() I realised that when I push into the accumulator instead of obtaining an array with a promise as element, acc is assigned to be a promise instead.
.then(responses=> {
return responses.reduce(async(acc, next) => {
acc.push(await(next.json()));
return acc;
}, [])
})
I can use the first version without any issue and the program works properly, but whats happening inside Array.reduce()? Why pushing a promise into the accumulator returns a promise intead of an array? How could I refactor the code with Array.reduce()?
Although it's not what you've asked, you could avoid the pain of having to use reduce, and just utilise the Promise.all() that you are already using:
return Promise.all(queries.map(q => q.then(res => res.json()))
.then(data => {...})
It's a much shorter way and less of a headache to read when you come back to it.
Have the accumulator's initial value be a Promise that resolves to an empty array, then await the accumulator on each iteration (so that all prior iterations resolve before the current iteration runs)
.then(responses=> {
return responses.reduce(async (accPromiseFromLastIter, next) => {
const arr = await accPromiseFromLastIter;
arr.push(await next.json());
return arr;
}, Promise.resolve([]))
})
(That said, your original code is a lot clearer, I'd prefer it over the .reduce version)
Live demo:
const makeProm = num => Promise.resolve(num * 2);
const result = [1, 2, 3].reduce(async(accPromiseFromLastIter, next) => {
const arr = await accPromiseFromLastIter;
arr.push(await makeProm(next));
return arr;
}, Promise.resolve([]));
result.then(console.log);
Unless you have to retrieve all data in serial, consider using Promise.all to call the .json() of each Promise in parallel instead, so that the result is produced more quickly:
return Promise.all(queries)
.then(responses => Promise.all(responses.map(response => response.json())));
If the queries are an array of Responses that were just generated from fetch, it would be even better to chain the .json() call onto the original fetch call instead, eg:
const urls = [ ... ];
const results = await Promise.all(
urls.map(url => fetch(url).then(res => res.json()))
);
This way, you can consume the responses immediately when they come back, rather than having to wait for all responses to come back before starting to process the first one.

Data not accessible outside Promise.all()

I have the below code to access movie sessions from a cinema site. I am looping using a while loop to fetch movie sessions.
And I intend to add the sessions within the loop to array sessionResults which is declared outside the while loop.
R. refers to the Ramda library
let page // passed as an argument to the outer function
let sessionResults = [];
while (currentCinemaIndex < cinemaList.length) {
await page.goto("www.fakeurl.com");
const _movies = await movies({ page });
//Get the sessions for each #_movies
const _movieSessions = await _movies.map(
async (_movie, index) => {
//sessions() returns an array of objects
const res = (await sessions({ page: page }, index + 1)).map(session => {
return Object.assign({}, _movie, session);
});
return res;
},
{ page }
);
//!!! AREA OF CONCERN
console.log(_movieSessions); // array of promises
Promise.all(_movieSessions).then(p => {
sessionResults = R.concat(R.flatten(p), sessionResults);
// console.log(sessionResults); // concatenated array
});
console.log(sessionResults); // []
//while loop logic
currentCinemaIndex = //increment currentCinemaIndex
limit =// set new limit
If you look at //!!! AREA OF CONCERN I have documented the value of sessionResults at different places.
Could you please advise why the value of sessionResults is not carried through outside Promise.all()?
You don't get the updated value of sessionResults because by the time the code executes until the console.log(sessionResults) after Promise.all(...), the promise has not resolved yet.
Therefore, the sessionResults returned by console.log is not yet updated.
What you can do instead is use await like below:
p = await Promise.all(_movieSesions);
sessionResults = R.concat(R.flatten(p), sessionResults);
console.log(sessionResults);
Please note that if you would use await like above, you need to do it inside an async function scope and not the global scope (because it is not async).
await Promise.all() worked based on comment from #CertainPerformance
Revised code looks like
sessionResults = await Promise.all(_movieSessions).then(p => R.flatten(p));
console.log(sessionResults); // concatenated array

Push to external object from inside a map function with a promise

I know this is a hot topic on stackoverflow, but I'm running into an issue while filling an external object with a promise function.
So basically what I want to do:
Through a promise get an array of objects I want to iterate over
Iterate over this array with a map function. Call a promise with each iteration
After this second promise resolves I want to push an Id and the result of the promise to an array
Naturally I cannot use a global object, because the promise will not be in the right scope. I have also experimented with Bluebird Promise.map function, but this way I am not able to push to the object more than only the second promise results.
Here is my code so far. PromiseTwo should populate an object, which I want to show in the res.json function (this is an Express/NodeJS app)
let promise = actie.groupActies()
let akties = {}
promise.then(function(aktieMaanden) {
let values = aktieMaanden.forEach((aktie) => {
let aktieId = aktie['_id']
let artikelen = aktie['artikelen']
let promiseTwo = order.getActieArtikelenOmzet(artikelen)
promiseTwo.then(function(orders) {
akties[aktieId] = orders
return akties
})
})
return akties
}).then((akties) => {
res.json({ message: "Omzet voor aktie", akties: akties })
})
Through a promise get an array of objects I want to iterate over
actie.groupActies()
Iterate over this array with a map function. Call a promise with each iteration
.then( acties => Promise.all(
acties.map(actie =>
order.getActieArtikelenOmzet(actie.artikelen)
.then(orders => [actie._id,orders])
)
))
After this second promise resolves I want to push an Id and the result of the promise to an array
.then(results=> res.json({results}))
The main idea here is to use Promise.all so that it only continues if all orders have finished.
Elaborating on the answer of Jonas w, using Promise.map the following works as well:
actie.groupActies()
.then(acties =>
Promise.map(acties, actie =>
order.getActieArtikelenOmzet(actie.artikelen)
.then(orders => [actie._id,orders])
)
)
.then(results => {
res.json({ message: "Omzet voor aktie", akties: results})
})

Categories