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

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

Related

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.

modify array after promise.all in foreach loop

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.)

Resolve array of promises one after another

I've found lots of solutions for this, typically something like
const serial = funcs =>
funcs.reduce((promise, func) =>
promise.then(result =>
func().then(Array.prototype.concat.bind(result))),
Promise.resolve([])
)
I'm trying to map an array of promises and run them one after another,
serial(Object.keys(tables).map(key =>
websocketExecute(store,dropTableSQL(tables[key]),null)))
.then(data => {console.log(data);success(data)})
They all run however I get an error TypeError: func is not a function
And the final then isn't resolved..
Any idea how I run a final .then() on a list of promises?
Your function serial expects its argument to be an array of functions that return Promises
however,
Object.keys(tables).map(key => websocketExecute(store,dropTableSQL(tables[key]),null))
returns an array of the results of calling
websocketExecute(store,dropTableSQL(tables[key]),null)
Which is not likely to be a function returning a promise, more like some result
What you'll want to do is:
serial(Object.keys(tables).map(key => () => websocketExecute(store,dropTableSQL(tables[key]),null)))
.then(data => {console.log(data);success(data)})
Assuming websocketExecute returns a Promise
So now, the array returned by .map is an array of
() => websocketExecute(store,dropTableSQL(tables[key]),null)
Which will get called in turn in .reduce
Check out Promise.all() as well.
If I'm not mistaken, you should be able to do something like the following:
const promises: Promise<any>[] = Object.keys(tables).map(key => (
websocketExecute(store, dropTableSQL(tables[key]), null)
)
Promise.all(promises).then((results: any[]) => { ...do stuff })
Typescript annotations are for readability.

not pushing elements in forEach statement

I have an object:
This is the function I use to loop through the object:
function getAllUsersKeys(user){
var promise = new Promise((resolve, reject) => {
var tokens = []
user.forEach(function(user) {
admin.database().ref(`/FAVORITES/${user.key}`).orderByKey().once('value').then((favShops) => {
favShops.forEach((shop)=>{
if(shop.key==saledata.store_id){ //if store that activated sale if found under users list under /FAVORITES
admin.database().ref(`/FAVORITES/${user.key}/token`).orderByKey().once('value').then((token) => {
//console.log(token.val().id);
tokens.push(token.val().id);
})
}
})
})
})
resolve(tokens);
})
return promise;
}
the console message will print out the "id" under selected token.
//console.log(token.val().id);
but when I push this "id" into the array called tokens nothing appears.
tokens.push(token.val().id);
Basically what I want this method to do is return an array with a selected amount of "id".
Please help me optimise my code. Right now it returns an empty array.
The issue is that you are resolving your getAllUserKeys promise before the firebase promises are resolved. The reason your console.log works is because it waits until your firebase lookup is done before logging. When you resolve(tokens), the promises that fill that array haven't resolved yet.
You would need to make sure that all of your firebase promises have resolved before you resolve(tokens).
UPDATE:
I'm not easily able to test this, so it likely won't work first try.
function getAllUserKeys(user) {
return Promise.all(user.map(user => {
return admin.database().ref(`/FAVORITES/${user.key}`).orderByKey().once('value');
})).then(favShops => {
return Promise.all(favShops.filter(shop => {
if(shop.key === saledata.store_id) {
return admin.database().ref(`/FAVORITES/${user.key}/token`).orderByKey().once('value');
}
return false;
}))
}).then(tokens => {
return tokens.map(token.val().id);
})
}
This is an example of how you would chain promises.
Convert an array of users into and array of promises from firebase, this will resolve to favShops.
In the then method you will get the result of the promise all, which should be an array of favShops.
Take the favShops and filter out only the shops that match the saledata.store_id, returning promises that will be resolve by the firebase api.
Once all the filtered shops promises have resolved you will take the array of tokens and convert them into an array of ids.
Use getAllUserKeys().then(tokens => {}) where tokens should be an array of ids.

async/await inside arrow functions (Array#map/filter)

I'm getting compile time error in this code:
const someFunction = async (myArray) => {
return myArray.map(myValue => {
return {
id: "my_id",
myValue: await service.getByValue(myValue);
}
});
};
Error message is:
await is a reserved word
Why can't I use it like this?
You can't do this as you imagine, because you can't use await if it is not directly inside an async function.
The sensible thing to do here would be to make the function passed to map asynchronous. This means that map would return an array of promises. We can then use Promise.all to get the result when all the promises return. As Promise.all itself returns a promise, the outer function does not need to be async.
const someFunction = (myArray) => {
const promises = myArray.map(async (myValue) => {
return {
id: "my_id",
myValue: await service.getByValue(myValue)
}
});
return Promise.all(promises);
}
If you want to run map with an asynchronous mapping function you can use the following code:
const resultArray = await Promise.all(inputArray.map(async (i) => someAsyncFunction(i)));
How it works:
inputArray.map(async ...) returns an array of promises - one for each value in inputArray.
Putting Promise.all() around the array of promises converts it into a single promise.
The single promise from Promise.all() returns an array of values - the individual promises each resolve to one value.
We put await in front of Promise.all() so that we wait for the combined promise to resolve and store the array of resolved sub-promises into the variable resultArray.
In the end we get one output value in resultArray for each item in inputArray, mapped through the function someAsyncFunction. We have to wait for all async functions to resolve before the result is available.
That's because the function in map isn't async, so you can't have await in it's return statement. It compiles with this modification:
const someFunction = async (myArray) => {
return myArray.map(async (myValue) => { // <-- note the `async` on this line
return {
id: "my_id",
myValue: await service.getByValue(myValue)
}
});
};
Try it out in Babel REPL
So… it's not possible to give recommendation without seeing the rest of your app, but depending on what are you trying to do, either make the inner function asynchronous or try to come up with some different architecture for this block.
Update: we might get top-level await one day: https://github.com/MylesBorins/proposal-top-level-await
it will be 2 instructions, but just shift "await" with the extra instruction
let results = array.map((e) => fetch('....'))
results = await Promise.all(results)
I tried all these answers but no one works for my case because all answers return a promise object not the result of the promise like this:
{
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(3)
0: ...an object data here...
1: ...an object data here...
2: ...an object data here...
length: 3
[[Prototype]]: Array(0)
}
Then I found this answer https://stackoverflow.com/a/64978715/8339172 that states if map function is not async or promise aware.
So instead of using await inside map function, I use for loop and await the individual item because he said that for loop is async aware and will pause the loop.
When you want each remapped value resolved before moving on to the next, you can process the array as an asynchronous iterable.
Below, we use library iter-ops, to remap each value into promise, and then produce an object with resolved value, because map itself shouldn't be handling any promises internally.
import {pipe, map, wait, toAsync} from 'iter-ops';
const i = pipe(
toAsync(myArray), // make asynchronous
map(myValue => {
return service.getByValue(myValue).then(a => ({id: 'my_id', myValue: a}))
}),
wait() // wait for each promise
);
(async function() {
for await (const a of i) {
console.log(a); // print resulting objects
}
})
After each value, we use wait to resolve each remapped value as it is generated, to keep resolution requirement consistent with the original question.

Categories