How to properly use async in forEach in nodeJS [duplicate] - javascript

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
I am using this forEach loop to push some data into array and send that to client side
var arrayToSend = [] //Empty Array
SomeArray.forEach(function(item){
Collection.findById(item.id,function (err,data) {
if (err) {
console.log(err)
} else {
arrayToSend.push(data.record)
}
//How can I run this next line at end of loop asynchronously
res.render("index",{arrayToSend : arrayToSend })
})
})
}
How can I use async to run res.render("index",{arrayToSend : arrayToSend }) after an loop end within forEach scope in form of callback,otherwise if I run this outside the loop it will send empty array ?

Instead of .forEach, use .map to map each item in someArray to a Promise, so that you can create an array of Promises. If you call Promise.all on that array, you'll get a Promise that resolves when all of the promises in the array have resolved.
Because findById is callback-based, not Promise-based, you need to transform each callback into a Promise first:
const promises = someArray.map((item) => new Promise((resolve, reject) => {
Collection.findById(item.id, (err,data) => {
if (err) return reject(err);
resolve(data.record);
});
}));
Promise.all(promises)
.then(arrayToSend => {
res.render("index", { arrayToSend });
})
.catch((err) => {
// handle errors
});
If you want to tolerate errors from findById and still build an arrayToSend without the items that had errors, you can push to an external array (similar to what you were doing originally) and resolve unconditionally, whether findById results in an error or not:
const arrayToSend = [];
const promises = someArray.map((item) => new Promise((resolve, reject) => {
Collection.findById(item.id, (err,data) => {
if (err) console.log(err);
else arrayToSend.push(data.record);
resolve();
});
}));
Promise.all(promises)
.then(() => {
res.render("index", { arrayToSend });
})

Related

Iterate over array of queries and append results to object in JavaScript

I want to return results from two database queries in one object.
function route(start, end) {
return new Promise((resolve, reject) => {
const queries = routeQuery(start, end);
var empty_obj = new Array();
for (i=0; i<queries.length; i++) {
query(queries[i], (err, res) => {
if (err) {
reject('query error', err);
console.log(err);
return;
} else {
empty_obj.push(res.rows);
}});
}
console.log(empty_obj);
resolve({coords: empty_obj});
});
}
This is my code right now, the queries are working fine but for some reason, pushing each result into an empty array does not work. When I console log that empty object, it stays empty. The goal is to resolve the promise with the generated object containing the two query results. I'm using node-postgres for the queries.
Output of res is an object:
{
command: 'SELECT',
rowCount: 18,
oid: null,
rows: [
{ ...
I suggest you turn your query function into a Promise so that you can use Promise.all:
// turn the callback-style asynchronous function into a `Promise`
function queryAsPromise(arg) {
return new Promise((resolve, reject) => {
query(arg, (err, res) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(res);
});
});
}
Then, you could do the following in your route function:
function route(start, end) {
const queries = routeQuery(start, end);
// use `Promise.all` to resolve with
// an array of results from queries
return Promise.all(
queries.map(query => queryAsPromise(query))
)
// use `Array.reduce` w/ destructing assignment
// to combine results from queries into a single array
.then(results => results.reduce(
(acc, item) => [...acc, ...item.rows],
[]
))
// return an object with the `coords` property
// that contains the final array
.then(coords => {
return { coords };
});
}
route(1, 10)
.then(result => {
// { coords: [...] }
})
.catch(error => {
// handle errors appropriately
console.error(error);
});
References:
Promise.all - MDN
Array.reduce - MDN
Destructing assignment - MDN
Hope this helps.
The issue you currently face is due to the fact that:
resolve({coords: empty_obj});
Is not inside the callback. So the promise resolves before the query callbacks are called and the rows are pushed to empty_obj. You could move this into the query callback in the following manner:
empty_obj.push(res.rows); // already present
if (empty_obj.length == queries.length) resolve({coords: empty_obj});
This would resolve the promises when all rows are pushed, but leaves you with another issue. Callbacks might not be called in order. Meaning that the resulting order might not match the queries order.
The easiest way to solve this issue is to convert each individual callback to a promise. Then use Promise.all to wait until all promises are resolved. The resulting array will have the data in the same order.
function route(start, end)
const toPromise = queryText => new Promise((resolve, reject) => {
query(queryText, (error, response) => error ? reject(error) : resolve(response));
});
return Promise.all(routeQuery(start, end).map(toPromise))
.then(responses => ({coords: responses.map(response => response.rows)}))
.catch(error => {
console.error(error);
throw error;
});
}

wait for Promise.all To continue executing

I would like to insert products into array and then continue using that array. I don't have an expirience using Promisses. Bellow is my code.
This is the function that I would like to return an array.
const findProduct = async function(request) {
const products = [];
await Promise.all(
request.products.forEach(async product => {
await db
.get()
.collection('products')
.findOne({ _id: product.productId.toString() }, (err, result) => {
if (err) throw new Error('exception!');
products.push(result);
});
})
)
.then(() => {
return products;
})
.catch(e => e);
};
Products is always undefined.
Shoud I maybe return Promise.all i save it into an array, then use that array?
This is how I plan to use array
const purchase = new Purchase(req.body);
const products = await findProduct(req.body);
purchase.products = [...products];
In the context of your findProduct function, using async/await syntax doesn't make a lot of sense if you intend to await the result of findProduct using const products = await findProduct(req.body);
Instead you want to define your function using only Promises.
Firstly, db.collection.findOne() makes use of a callback for asynchronous events, to use this in a Promise chain, you must wrap this in a Promise. Which can be done like so:
new Promise((resolve, reject) => {
db
.get()
.collection('products')
.findOne({ _id: product.productId.toString() }, (err, result) => { err ? reject(err) : resolve(result) })
});
Here (err, result) => { err ? reject(err) : resolve(result) } is just a concise form of
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
Next, you must pass an array to Promise.all(). Because you already have the array request.products, you can convert (or map) each value to it's corresponding Promise to get it's data. This is done using Array.prototype.map. When used this way, Promise.all() will resolve to an array of product data objects in the same order of request.products.
Mixing this in to your code, yields:
const findProduct = function(request) {
return Promise.all(
request.products.map(product => {
return new Promise((resolve, reject) => {
db
.get()
.collection('products')
.findOne({ _id: product.productId.toString() }, (err, result) => err ? reject(err) : resolve(result))
});
})
);
};
Note here that I removed .then(()=>products) (because Promise.all will return products itself thanks to the promise above) and .catch(e=>e) (because you shouldn't be ignoring errors like this).
With these changes, you can now use findProduct as you have used it in your question.
Update:
Seems that the MongoDB API is Promises compliant, which means we can remove the Promise callback wrapper and simplify it down to:
const findProduct = function(request) {
return Promise.all(
request.products.map(product => {
return db
.get()
.collection('products')
.findOne({ _id: product.productId.toString() });
})
);
};
Promise.all() expects an array of Promises and map will return an array. Since db will also return a Promise we just feed those promises into Promise.all()
Edit: forgot to return Promise.all() which returns a promise so it can be consumed with the await findproducts() or findproducts().then()
const findProduct = function (request) {
return Promise.all(
request.products.map(product => {
return db
.get()
.collection('products')
.findOne({ _id: product.productId.toString() }
});
})
)};
Since quite a lot of people ask: "How do i get something from a Promise?" --> use the resolve value
Here a long explanation why this works: wait() will return a Promise that is mapped into an array with map() which returns this array. So when map is done we have an array with 3 Promises returned by the 3 wait() calls. This array is passed into Promise.all() which will wait for all the Promises to resolve and resolves itself to an array with all the resolve values of the Promises from the array. Since findProduct() will return this Promise from Promise.all() when can access it with findProduct().then() or await findProduct()
const start = { products: ['milk', 'butter', 'eggs'] }
const findProduct = function (request) {
return Promise.all(
request.products.map(product => {
return wait(product)
})
)
};
function wait(product) {
return new Promise((resolve) => {
setTimeout(() => { resolve(product) }, 5000)
})
}
findProduct(start).then(res => console.log(res))
// logs [ 'milk', 'butter', 'eggs' ]
Edit: To answer the comment about .then() returning a Promise
findProduct(start)
.then(res => console.log(res))
.then(res => console.log(res))
// logs [ 'milk', 'butter', 'eggs' ] and undefined
findProduct(start)
.then(res => {console.log(res); return res})
.then(res => console.log(res))
// logs 2x [ 'milk', 'butter', 'eggs' ]

Transform forEach in Promise (without ASYC/AWAIT the legacy code) [duplicate]

This question already exists:
Transform forEach in Promise (without ASYC/AWAIT) [duplicate]
Closed 3 years ago.
I need to transform a forEach in promise. The code is legacy and I can't use async/await operators.
Promise.all(Object.entries(data).forEach(function (data) {
let [data1, data2] = data
let info;
consultData.getResponse(data1).then(result => info = result).then(function () {
return dataServices.find(info)
.then(function (result) {
// do things
})
.then(function (info) {
// do final things
})
})
})).then((result) => {
// do something when all things have intereted and finished
})
But output that Promise.all can't be used. If I try with Promise.resolve, the last is printed before all things have finished their processing.
How I can transform the forEach in a promise for i can use .then() after all iteration?
ASYNC/AWAIT DON'T WORK IN THIS CODE
As #Jonas Wilms indicated, you can use .map returning each promise and after that you can use Promise.all
const entries = Object.entries(data);
const arrayOfPromises = entries.map((item) => {
return new Promise((resolve, reject) => {
const [data1, data2] = item;
consultData.getResponse(data1).then((result) => {
return dataServices.find(info);
}).then((infoServiceResult) => {
return resolve(infoServiceResult);
}).catch((err) => {
return reject(err);
});
});
});
Promise.all(arrayOfPromises).then((data) => {
// data is an array of each infoServiceResult
console.log(data);
}).catch((err) => {
console.error(err);
});

Even after pushing element in the array.Array is showing empty outside the loop

I'm trying to upload multiple images using cloudinary in node.js application.
Storing every image URL in an array. But my array is empty outside the loop. Can't understand why.
const postCreate = (req,res,next) => {
req.body.post.images = [];
const file_length = req.files.length;
let arr = [];
//console.log(req.files);
new Promise((resolve, reject) => {
req.files.forEach((file,index) => {
i = index;
cloudinary.v2.uploader.upload(file.path)
.then(image => {
//console.log(image);
req.body.post.images.push({
url: image.secure_url,
public_id: image.public_id
});
console.log("array", req.body.post.images);//there array is containing the element which is pushed.
});
console.log("arr", req.body.post.images);//but there it is showing empty array .Can't understand why array is empty.
});
resolve();
}).then(() => {
Post.create(req.body.post)
.then(post => {
//console.log(req.body.post.images);
res.redirect(`/posts/${post.id}`);
}).catch(err => {
console.log('Error will saving posts from db ', err);
return next(err);
});
});
Each of the uploads is asynchronous and returns a promise.
You need to have all those promises resolve before moving on to the final then()
You can map an array of those promises and use Promise.all() to return the full array to the final then()
Something like:
const doUpload = (file) => {
// return the upload promise
return cloudinary.v2.uploader.upload(file.path).then(image => {
return {
url: image.secure_url,
public_id: image.public_id
};
});
}
const postCreate = (req, res, next) => {
// map array of individual upload promises
const uploadPromises = req.files.map(doUpload);
Promise.all(uploadPromises).then(imagesArray => {
// assign new array to post body
req.body.post.images = imagesArray;
Post.create(req.body.post)
.then(post => {
//console.log(req.body.post.images);
res.redirect(`/posts/${post.id}`);
}).catch(err => {
console.log('Error will saving posts from db ', err);
return next(err);
});
}).catch(err=> console.log('One of the uploads failed'));
}
The 2nd log message actually gets called first while the array is empty because the code in the then block is waiting for something asynchronous to complete.
your problem is your print function fire before loop is complated so you have to use async-await for proper solution and learn more about this topic
please refer https://blog.risingstack.com/mastering-async-await-in-nodejs for your solution
it describe the async await for your proper output

Node.js: Unable to return new array from array.map()

I am using a package called Okrabyte to extract words from each image file in a folder. The result should be a new array containing the extracted text that I can use in other functions.
When I run this:
var fs = require("fs");
var okrabyte = require("okrabyte");
fs.readdir("imgs/", function(err, files){
files.map((file)=>{
okrabyte.decodeBuffer(fs.readFileSync("imgs/"+ file), (err, data)=>{
let splitWords = data.split(" ");
let word = splitWords[0].substr(1);
console.log(word);
})
})
})
the console logs each word. To return an array with those words I've tried the following:
async function words() {
await fs.readdir("imgs/", function (err, files) {
return files.map(async (file) => {
await okrabyte.decodeBuffer(fs.readFileSync("imgs/" + file), async (err, data) => {
let splitWords = data.split(" ");
let word = splitWords[0].substr(1);
return word
})
})
})
}
var testing = await words();
console.log(testing);
This gives undefined I've tried turning everything into a promise, I've tried async-await, I've tried pushing each word into a new array and returning that array in closure but nothing works - what am I doing wrong??
If your map function is async then it's returning a promise, so your mapped array is in fact an array of promises. But you can then use a Promise.all to get the resolved values of that array.
Additionally, you're trying to await the call to fs.readdir and okrabyte.decodeBuffer, which both accept a callback and do not return a promise. So if you want to use a promise there you'll have to wrap them in a promise constructor manually.
Here's how I would do it:
async function words() {
// Wrap `fs` call into a promise, so we can await it:
const files = await new Promise((resolve, reject) => {
fs.readdir("imgs/", (err, files) => { err ? reject(err) : resolve(files); });
});
// Since map function returns a promise, we wrap into a Promise.all:
const mapped = await Promise.all(files.map((file) => {
// Wrap okrabyte.decodeBuffer into promise, and return it:
return new Promise((resolve, reject) => {
okrabyte.decodeBuffer(fs.readFileSync("imgs/" + file), (err, data) => {
if (err) return reject(err);
const splitWords = data.split(" ");
const word = splitWords[0].substr(1);
resolve(word);
})
})
}))
// Mapped is now an array containing each "word".
return mapped;
}
var testing = await words();
// Should now log your array of words correctly.
console.log(testing);
You should not be using async-await that way. That should be used when you are dealing with promises. The library okrabyte uses the concept of callbacks.
I suggest you follow this approach:
(1) Enclose the okrabyte.decodeBuffer part in a function that returns a promise that resolves in the callback.
(2) Use files.map to generate an array of promises calling the function you defined in (1)
(3) Use Promise.all to wait for all promises to execute and finish before moving on to dealing with all the words.
Walkthrough:
Part 1
const processWord = (file) => {
return new Promise((resolve, reject) => {
okrabyte.decodeBuffer(fs.readFileSync("imgs/"+ file), (err, data)=>{
if (err) {
reject(err); // <--- reject the promise if there was an error
return;
}
let splitWords = data.split(" ");
let word = splitWords[0].substr(1);
resolve(word); // <--- resolve the promise with the word
})
});
}
You make a function that wraps the decoding part into a promise that eventually resolves with the word (or is rejected with an error).
Part 2
const promises = files.map((file)=>{
return processWord(file);
})
The above will generate an array of promises.
Part 3
fs.readdir("imgs/", function(err, files){
const promises = files.map((file)=>{
return processWord(file);
})
Promise.all(promises)
.then(responses => {
// responses holds a list of words
// You will get each word accessing responses[0], responses[1], responses[2], ...
console.log(responses);
})
.catch(error => {
console.log(error); // Deal with the error in some way
});
})
The above uses Promise.all to wait for all promises to resolve before going to the then() block, assuming no errors occurred.
You can further isolate the construct above in a method that will return a promise with a list of all the words, much in the same fashion that was done in the processWord function from Part 1. That way, you can finally use async-await if you wish, instead of handling things in the then() block:
const processEverything = () => {
return new Promise((resolve, reject) => {
fs.readdir("imgs/", function(err, files){
const promises = files.map((file)=>{
return processWord(file);
})
Promise.all(promises)
.then(responses => {
resolve(responses);
})
.catch(error => {
reject(error);
});
})
});
};
const words = await processEverything();
console.log(words);
You're returning word value to the enclosed function but not map() function.
Hope this code help you.
async function words() {
global.words = [];
await fs.readdir("imgs/", function(err, files){
return files.map( async(file)=>{
await okrabyte.decodeBuffer(fs.readFileSync("imgs/"+ file), async (err, data)=>{
let splitWords = data.split(" ");
let word = splitWords[0].substr(1);
global.words.push(word);
})
})
})
}
var testing = await words();
testing = global.words;
console.log(testing);

Categories