How can I fill the array after forEach loop - javascript

const tileApiPromises = [];
response.data.forEach((tile) => {
return getPartnerToken(tile.id).then((response) => {
tileApiPromises.push(getTileContent(tile, response));
});
});
console.log(tileApiPromises) // should give me an array of promises
Promise.all(tileApiPromises) // The goal is to execute this.
I am getting empty array of course in the log. How can I get array with promises outside the forEach. Thanks for your help!

The problem with your code is that the push is done asynchronously - therefore, there's nothing in the array yet!
you say you tried map (in the comment) - did you try it like this?
const tileApiPromises = response.data.map((tile) => {
return getPartnerToken(tile.id).then((response) => {
return getTileContent(tile, response);
});
});
or, sexier
const tileApiPromises = response.data.map(tile =>
getPartnerToken(tile.id).then(response => getTileContent(tile, response))
);

You can use Array.map function instead of forEach.
forEach always return undefined.
while map function iterates over array provided and returns accumulated array of values returned in callback provided in map function.
Here is nice documentation on map function.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Related

How can I push elements out of a forEach() context?

This is my first post ever :)
So what I am trying to do is loop through keys of an object with a forEach() loop and for each element, push specific values into an array. Then after the loop, resolve the array containing all pushed values.
As I understand, it's hard to get out values of the forEach() context. Is there a way to do something like that ?
Here's a code exemple of what I'm trying to do:
some function returning a promise
...
...
let promisesArray = [];
//Loop on each object from data and do whatever
ObjectJSON.array.forEach((object) => {
if (object.key === "foo") {
functionBar()
.then((response) => {
promisesArray.push(
`Object: ${object.key} | ${object.someOtherKey}`
);
})
.catch((error) => {
reject(error);
});
}
});
resolve(promisesArray);
}); //end of promise's return
Right now, it returns an empty array.
The expected result should be something like this instead:
[
'Object: key1 | someOtherKey1',
'Object: key2 | someOtherKey2',
'Object: key3 | someOtherKey3',
...
]
Thanks a lot for your answers !
Congrats on your first post ever!
It's a super classic question though, how to use an asynchronous function in a loop. With .forEach you can't, but you can using await in a for loop :
let promisesArray = [];
for (let object of ObjectJSON.array) {
if (object.key !== "foo") continue;
const response = await functionBar();
promisesArray.push(
`Object: ${object.key} | ${object.someOtherKey}`
);
}
resolve(promisesArray);
Using await will probably require your function to be marked as async (async function doSomething() { ... )

Transform class method from callback flow to promise

I have the following sample code,
class GetResultsFromDatabase{
results : SiteTable[];
db : AccessDatabase;
constructor(){
this.db = new AccessDatabase();
}
getAllLevelsSites(){
this.db.getSitesDb(this.fullFillSites);
}
private fullFillSites(data : SiteTable[]){
this.results= data;
this.db.getUrlsDb(this.fullFillUrls);
}
private fullFillUrls(data : UrlsTable[]){
data.map( (current) => this.results[this.results.findIndex(obj => obj.id =
current.token)].urlTable = current );
}
}
If some code outside class calls the method "getAllLevelsSite" i want to return the complete results array with all the values fullfilled (after the last "fullFillUrls function complete).
The "db" object uses the mysql library so all the methods works only with callbacks.Which options do i have ? Is it possible to create any kind of Promise in getAllLevelsSite method in a way that the code that is calling it can use async await syntax ? Or the only way is to pass one callback function to the method getAllLevelsSite ?
Thanks in advance.
Best regards.
Your example does not provide impl. for getSitesDb so will guess how the callback looks like...
getAllLevelsSites() {
return new Promise((resolve, reject) => {
this.db.getSitesDb(this.fullFillSites, (arr, err) => {
if (err) {
reject(err);
} else {
resolve(arr);
}
});
});
}
Now wherever you are consuming this method you can use await
Untested (so I could be wrong), but rewriting getAllLevelsSites method to something like the below might work:
getAllLevelsSites(): Promise<SiteTable[]> {
return new Promise((resolve) => {
this.db.getSitesDb(resolve);
});
}
Based on the signature of fullFillSites, I've assumed that the callback passed to this.db.getSitesDb will be invoked with at least one argument (of type SiteTable[]).
Since getAllLevelsSites returns in a promise (in this code above), you should be able to then use async/await syntax.
Unrelated: You use Array.prototype.map to iterate over data in fullFillUrls, but don't do anything with the array that map returns. Seems like you should swap map out for forEach.

Promises being evaluated before Promise.all in nested forEach, resulting in empty Promise.all

I'm getting some unusual behaviour.
Basically as part of my code I have a function which utilizes nested for loops to build a promise and add it to a list of promises.
After the nested loops are complete, I'd like to evaluate the promise list using promise.all().
I have successfully managed to do this with a single forEach loop in the past, the nesting seems to cause some issues, namely, testing reveals that the Promise.all is being called before the nested forEach loop terminates, resulting in it being called on an empty list and hence returning an empty list.
I have a feeling that the issue is that I'm missing a return statement somewhere in my nested forEach loop as mentioned in this answer but I have not been able to determine where.
culprit.js
const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")
function nestedFunction(list){
var promises = [];
list.forEach(element => {
otherModule.getSublist(element).then(sublist => {
sublist.forEach(subelement => {
promises.push(otherOtherModule.promiseResolvingFunction(subelement));
});
});
});
return Promise.all(promises);
}
module.exports = {
nestedFunction : nestedFunction
}
culprit.test.js
const culprit = require("culpritpath")
// for mocking
const otherModule = require("blablabla")
otherModule.getSublist = jest.fn(() => Promise.resolve([{}, {}, {}]))
const otherOtherModule = require("blablabla2")
otherOtherModule.promiseResolvingFunction = jest.fn(() => Promise.resolve())
describe("nestedFunction()", ()=>{
it("returns an array of resolved promises", () => {
return culprit.nestedFunction([{}, {}]).then(res => {
expect(res).toHaveLength(6);
})
})
})
instead I get that res is []. Further tests show that promiseResolvingFunction is being called the right number of times, so as I understand, Promise.all is being called before the nested forEach loop finishes.
PS: I am still getting started with promises and TDD, I am more than happy to hear feedback on any code smell.
Yeah so the problem I see is that your for each loop is calling asynchronous code and expecting it to execute synchronously.
I'd probably do something like...
var promises = list.map(element => {
return otherModule.getSublist(element).then(sublist => {
// Map all of the sublists into a promise
return Promise.all(sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement));
}));
});
});
return Promise.all(promises);
Of course then you'd end up with an array of arrays. If you wanted to keep the result a flat array of sublist items, another option would be to first fetch all of your lists, then fetch all of your sublists from those results...
return Promise.all(list.map( element => otherModule.getSublist(element)))
.then((sublists) => {
let subListPromises = [];
// Loop through each sublist, turn each item in it into a promise
sublists.forEach( sublist => {
sublistPromises = [
...sublistPromises,
sublist.map( subelement => otherOtherModule.promiseResolvingFunction(subelement))
]
})
// Return a promise dependent on *all* of the sublist elements
return Promise.all(sublistPromises)
})
You execute Promise.all before the array has been populated (which happens asynchronously).
It may look difficult to deal with nested promises, but just apply Promise.all to the inner arrays of promises, and then at the outer level, apply Promise.all to all those from the inner level.
Then you're not ready yet, as now you have a promise that resolves to an array of arrays (corresponding to the originally nested promises), so you need to flatten that with the quite new .flat method, or with [].concat:
function nestedFunction(list) {
// Get promise for the array of arrays of sub values
return Promise.all(list.map(element => {
return otherModule.getSublist(element).then(sublist => {
// Get promise for the array of sub values
return Promise.all(sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement);
}));
});
})).then(matrix => [].concat(...matrix)); // flatten the 2D array
}
You need to nest your promise resolution. Something like this:
const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")
function nestedFunction(list){
var promises =
list.map(element => {
return otherModule.getSublist(element).then(sublist => {
return Promise.all(
sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement);
})
);
});
});
return Promise.all(promises);
}
module.exports = {
nestedFunction : nestedFunction
}

How to chain a list of promises in RXJS?

How can i chain a list of promises in RXJS? Every promise needs to be executed when the previous is resolved (work todo is stateful).
The way i'm doing it now feels primitive:
const workTodo = []; // an array of work
const allWork = Observable.create(observer => {
const next= () => {
const currentTodo = workTodo.shift();
if (currentTodo ) {
doTodoAsync(currentTodo)
.then(result => observer.onNext(result))
.then(next);
} else {
observer.onCompleted();
}
};
next();
});
I was thinking something like this:
const workTodo = []; // an array of work
const allWork = Observable
.fromArray(workTodo)
.flatMap(doTodoAsync);
But that basically executes all promises at once.
It seems you were pretty close with your attempt.
You may either specify maximum concurrency of 1 for .flatMap like:
Observable.fromArray(workTodo)
.flatMap(doTodoAsync, 1)
or equivalently use .concatMap instead of .flatMap:
Observable.fromArray(workTodo)
.concatMap(doTodoAsync)
I would use concatMap as it feels more idiomatic.
UPDATE: DEMO
How about some recursion?
First create a recursive function and call it recursiveDoToDo:
const recursiveDoToDo = (currentTodo, index) =>
Observable
.fromPromise(doTodoAsync(currentTodo))
.map(resolved => ({resolved, index}));
The code above simply wraps your doTodoAsync into an Observable, and then we map the results to return the resolved promise and the index of the array, for recursion use later.
Next, we will recursively call the recursiveDoToDo with the .expand() operator.
recursiveDoToDo(worktodo[0], 0)
.expand(res => recursiveDoToDo(worktodo[res.index + 1], res.index + 1))
.take(worktodo.length)
All you need to do for your recursion is just to increment the index by 1. Because .expand() will recursively run forever, the .take() operator is there to tell the observable when to end the stream, which is the length of your worktodo.
Now you can simply subscribe to it:
recursion.subscribe(x => console.log(x));
Here is the working JS Bin

Trouble using forEach and mongoose's findById in tandem

I am writing a Node route which should push objects onto an array declared outside the forEach loop after the objects have a property added to them. When I console.log the array within the loop, it seems to be taking on data, but when I return it to the client-side. It is empty.
var todaysTopItemsBySaleFrequency = [];
listOfItemIdsAndSaleFrequency.forEach((item) => {
Product.findById(item.itemId).then((foundItem) => {
var fullItemData = foundItem.toJSON();
fullItemData.occurrences = item.occurrences;
todaysTopItemsBySaleFrequency.push(fullItemData);
console.log(todaysTopItemsBySaleFrequency);
});
});
return res.status(200).json(todaysTopItemsBySaleFrequency);
The console.log statement shows that the array called todaysTopItemsBySaleFrequency is being populated correctly, but why is it empty when I return it to the client?
The callback function you're passing to Product.findById(item.itemId).then(...) is not invoked immediately. Your outer forEach completes and you return before any of your callbacks are invoked.
Mongoose's findById() method returns a promise. You can use Promise.all() to wait for an array of promises to complete, and then set res.status(200).json(...). Because this happens asynchronously you should also present an asynchronous interface, for example by returning a promise yourself.
Here's a version that gathers all of the responses and returns a promise that resolves with your original return value:
var todaysTopItemsBySaleFrequency = [];
return Promise.all(listOfItemIdsAndSaleFrequency.map((item) => {
return Product.findById(item.itemId).then((foundItem) => {
var fullItemData = foundItem.toJSON();
fullItemData.occurrences = item.occurrences;
todaysTopItemsBySaleFrequency.push(fullItemData);
console.log(todaysTopItemsBySaleFrequency);
});
})).then(() => res.status(200).json(todaysTopItemsBySaleFrequency));

Categories