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

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

Related

How to add properties to object incrementally?

I am trying to map object where correct ID will contain the array that belongs to it.
Here is the scenario:
let obj = {}
const arr = []
for (let i = 0; i < someIds.length; i++) {
const res = await someApiCall() // returns array
obj = {
id: someIds[i],
res
}
arr.push(obj)
return arr
}
The thing here is that arr will only output last set of object because of iterating over the values.
For example I get
[{id: 122, res: "whatever122"}]
where I want to get
[{id: 122, res: "whatever122"}, {id: 111, res: "whatever111"}, {id: 133, res: "whatever133}]
How do I concatenate all?
You return arr to fast. It should be returned outside the for scope:
let obj = {}
const arr = []
for (let i = 0; i < someIds.length; i++) {
const res = await someApiCall() // returns array
obj = {
id: someIds[i],
res
}
arr.push(obj)
}
return arr
Move the return statement outside of the for loop, which would wait for all of the objects to get added to the array.
let obj = {}
const arr = []
for (let i = 0; i < someArr.length; i++) {
const res = await someApiCall() // returns array
obj = {
id: res[i],
res
}
arr.push(obj)
}
return arr
The problem is produced by the return statement that should probably stay outside the for loop. It completes the execution of the current function and returns (an optional value) to the caller.
A simpler way to write what you need is to use Promise.all() to run all the calls to someApiCall() in parallel (if they are independent). This way the code run much faster:
async function doSomething() {
return await Promise.all(
someIds.map(
async (id) => {
return {
id: id,
res: await someApiCall(id),
};
}
)
);
}
How it works
someIds.map() calls the callback function that it receives as argument for each item of someIds and returns a new array that contains the values returned by the callback function.
Because the callback function is asynchronous and uses await, it returns a Promise as soon as the await-ed call starts, without waiting it to complete.
The array of promises returned by someIds.map() is passed as argument to Promise.all() that waits for all of them and returns an array containing the values returned by the promises (which are the objects returned by the callback were they be synchronous).
Remark
The await in front of Promise.all() is not really needed. Without it, the function doSomething() returns the promise returned by Promise.all(). With it, the function returns an array of objects. Either way, the call of doSomething() still needs to be await-ed and the value produced by it after await is an array of objects.

Simplifying a for loop to find an item in an array (returned asynchronously) by condition in JavaScript

I have a function called getItems that returns an array of objects asynchronously. Each object has a isOccupied method that returns a boolean. I wrote a function that takes an index and returns whether index-th item in the array's isOccupied is true or false.
async itemIsOccupied(index) {
return getItems().then(items => {
if (items.length - 1 < index) return false;
return items[index].isOccupied()
});
}
This just works fine, but the fact that I have to use an async function to fetch the array makes it lengthy. Is there a way to simplify this?
I'd clean it up a little like this...
async itemIsOccupied(index) {
const items = await getItems();
return index < items.length && items[index].isOccupied();
}

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

How can I fill the array after forEach loop

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

Categories