This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 1 year ago.
I was wondering what is the problem here, why is the return statement not waiting to finish the previous instruction? orders are empty, even though I'm logging inside the getOrders to make sure there were data (there is data).
await stores.forEach(async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
return orders;
To wait for all promises, you need an array of promises, which you can get using map. Then you need to use Promise.all, so that it will wait for all the promises in that array to complete.
const orders = await Promise.all(
stores.map(store => getOrders(store, token, tokenType))
);
return orders;
*Edit: rewritten and simplified based on suggestion from #PatrickRoberts
To paraphrase his comment, in the old code when using a separate array with an array.push, the array ordering would be indeterminate. However using the array of results from Promise.all will always match the order of the original array.
It's a common misunderstanding that asynchronous callbacks (declared with async and called with await) passed to forEach or map will be executed synchronously or in order. Actually, they are not.
When you check the polyfill of those methods, you'll find a line like this callback.call(T, kValue, k, O);. So, basically, it just execute the callback. If the callback is an asynchronous method, it doesn't wait its execution to be done. Instead, it continue executing other codes.
Let's set an example using your code, let' say this is the callback:
const callback = async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
When it was passed to forEach:
stores.forEach(callback);
basically, what forEach does is iterating the array, and call callback on each elements. May looks like this
...
loop {
callback.call(...)
}
...
There is no wait here. However, each time, the code inside your callback is executed synchronously since it's declared with async and called with await, which means after getOrders is resolved and the result is pushed to orders.
So, if you want to execute the callbacks synchronously and in order, you may write a generic for loop.
async function addOrders(){
for(let store of stores) {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
}
}
addOrders()
Related
I have an array [1,2,3,4,5] and when insert into database I except to receive result be like [1,2,3,4,5] in database. But only loop for give me an expected result, map() and forEach() always give me disorder array like [1,3,4,5,2] or [4,3,2,5,1] and so on.
This is my code:
arrayChild.map(async item => {
await this.repository.save(item)
});
arrayChild.forEach(async item => {
await this.repository.save(item)
});
for (let i = 0; i < arrayChild.length; i++) {
await this.repository.save(arrayChild[i])
}
Please give me the reason. Thank for your attention
because map and forEach methods don't handle the asynchronous function you passed in an async await way.
When you check the polyfill of both method, you'll find a line like this callback.call(T, kValue, k, O);. So, basically, it just execute the callback. If the callback is an asynchronous method, it doesn't wait its execution to be done. Instead, it continue executing other codes.
So, when you save your array of data to database, your callbacks inside map & forEach just issue a few query, the final order of database's execution may be uncertain.
Because await doesn't work in forEach and map methods, it is not asynchronous. Therefore, when you use await in forEach or map methods to save data into db, it will run the loops silmutaneously and the results will be in disordered way.
allDoc.map(function(stage){
for(let key of stage.layerItem){
//key.toImage is a library, so it only supports callback.
key.toImage({
callback(img) {
addPhoto(key.attrs)
}
});
}
})
// Run when addPhoto is done
console.log("done")
async function addPhoto(params){
const stored = await s3.upload(params).promise()
return stored.Location
}
When the addPhoto function is completed, how do I proceed to the next code?
First wrap the toImage function in one that returns a promise then use Promise.all to wait for them all to settle.
(Community Wiki because this is close to being a duplicate question but has two distinct steps each of which has their own duplicate).
One of the solutions I found for waiting .map() to be complete, is putting a Promise.all().
Your code should look like this:
const promisse = allDoc.map(function(stage, index){
// Your loop
})
await Promise.all(promisse);
console.log('done');
Keep in mind that if your allDoc.map is inside another function, the upper function must be async.
In JavaScript, I'm calling a promise which returns an id; we'll call this promise1. promise1 has a following .then() which, given the id returned from promise1, calls a for-each loop. On each iteration of this loop, promise2 is carried out
promsie2 also has a .then(). so the data returned from promise2 can be utilized.
mycode.js
exports.my_function = (req, res) => {
var data = req.body;
var name_array = ["Jeff", "Sophie", "Kristen"]
var personal_ID_arr = []
promise1.getID(data) //promise1 gets an int 'id'
.then(id => {
array.forEach(name => { //for each name
promise2.getPersonalID(name, id) //promise2 creates a personal id for each person using their name and the id generated from promise1
.then(personalID => {
personal_ID_arr.push(personalID) //add the generated personal id to an arr
})
})
})
//will need to operate on 'personal_ID_arr' here after the above finishes or possibly still within the promise1's .then; as long as the arr is filled fully
}
However, I'm running into the issue of synchronicity not allowing for this type of logic to happen, as things begin happening out of order once it gets to the loop aspect of the program. I need to carry out another function with the filled-out 'personal_ID_arr' after these promises are carried out as well
I've looked at other questions regarding promise chaining but this is a different case due to the fact the for loop needs to work with the data from the first promise and use .then() inside a .then(). Surely, I don't need to keep this structure though if a better one exists.
Any help would be greatly appreciated
Keeping an array of IDs that an asynchronous operation will fill, is not something that will help you carrying out more asynchronous operations on the array later. Instead, consider using an array of Promises for each name, and wait them all to resolve using Promise.all. Also, i removed the personal_ID_arr from the function my_function variables and moved it to the appropriate asynchronous operation. I did a simple console log of the array, you should fill your next step there.
Although maybe you need to write this logic with Promises, but I would suggest using async / await for this task, it will lead to more readable code.
exports.my_function = (req, res) => {
var data = req.body;
var name_array = ["Jeff", "Sophie", "Kristen"]
promise1.getID(data) //promise1 gets an int 'id'
.then(id =>
//map the array of Strings to array of Promises instead, wait for them to resolve
Promise.all(name_array.map(name =>
promise2.getPersonalID(name, id)
))
)
.then(personal_ID_arr => console.log(personal_ID_arr))
//will need to operate on 'personal_ID_arr' here after the above finishes or possibly still within the promise1's .then; as long as the arr is filled fully
}
I want to get the results of service calls nested in a forEach loop before executing the next block of code
I've tried using a plain for loop, that works... but ESLINT moans about it.. so what's the ideal solution?
const people = []
const fetchPeople = async () => {
peopleIds.forEach(personId => {
people.push(restApi.getPersonById(personId))
})
}
await fetchPeople()
logger.error(`people should be populated : ${JSON.stringify(people)}`)
I expect people to be populated by the end of the forEach so I can use the results in the next function.
Actually, people is not populated
Easiest way is with Promise.all() and array.map()
const people = await Promise.all(peopleIds.map(restApi.getPersonById));
logger.error(`people should be populated : ${JSON.stringify(people)}`)
Array.map() will create a promise for each of the ids, Promise.all will wait for all of them and output an array of their async returned values.
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
I have a forEach loop inside an async function, and inside that forEach are two additional forEach loops that perform AJAX requests. I want to return a resolved promise once all of the AJAX requests are complete.
First, the user clicks "Submit" and I call a function to handle submission, setting the submitting property to true. Once the submission is complete, I want to change the value of the submitting property.
submit() {
this.submitting = true;
if (creating) {
this.create().then(() => this.submitting = false)
}
}
Here's an approximation of the create() method:
async create() {
// Calls a function that uses axios to send a POST request to create
// a new group, returning its ID upon completion. This works fine.
const groupId = await this.createGroup();
// 'users' is an array of objects
this.users.forEach(async user => {
// Create a new user and get their ID. This works fine.
const userId = await this.createUser(info);
// Each 'user' object contains a key called 'attributes' whose
// value is an array of 'attribute' objects. I use axios to make a
// POST request to store the info about these attributes in the DB.
user.attributes.forEach(attribute => {
axios.post('/attributes', attribute)
// In my application, I'm actually calling a method that handles
// making the request, so it looks more like this:
// this.createAttributes(attribute)
}
// Each 'user' object also contains a key called 'images' whose
// value is an array of 'image' objects. Here again, I use axios to
// make a POST request to store the info about these images.
user.images.forEach(image => {
axios.post('/images', image)
}
}
// Once everything has successfully posted, I want to return a
// resolved promise so that the then() callback on my create method
// is executed. However, anything here gets executed before the AJAX
// requests complete.
console.log('All done');
}
I've tried a few different solutions I've come across for capturing promises using map() and using Promise.all to execute a callback once they're all complete, but I haven't had success with that. I think the issue stems from my having multiple nested forEach() loops, but I don't understand promises (and their behavior within forEach() loops specifically) well enough to know for sure.
It's also my understanding that I can use await inside a for...of loop, but I would prefer to execute the AJAX requests in parallel if possible.
Thanks!
Edit: The proposed duplicate is similar, but my question was specifically about async operations within nested forEach loops. While the solution revealed that there is nothing particularly unique about handling these cases (do the same thing on both the parent and nested loop), determining whether this was the case was the motivation for asking the question.
map to promises, then use Promise.all and await them:
await Promise.all(user.images.map(image =>
axios.post('/images', image)
));
Same applies to the main forEach:
await Promise.all(this.users.map(async user => {
//...
}));