This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 5 years ago.
Currently we are storing short strings as keys.
These keys correspond to long values which are labels.
I am trying to update the corresponding long value for the key.
But the console.log(record) always executes first and then the inner log statement executes which is not is desired. It always sends the unmodified record to the getRadioValues function caller.
I want to return the record after the corresponding key is updated.
export const getRadioValues = (record: IRecordInput) => {
const singleSelectKeys = ['Race', 'DeathWas', 'MannerOfDeath'];
singleSelectKeys.forEach(async key => {
if (record[key]) {
const dropDownOption = await DropDownOptions.find({ where: { id: record[key] }}) as IPDFSelect;
record[key] = dropDownOption.dataValues.Text;
console.log(record[key]);
}
});
console.log(record);
return record;
};
Your forEach is using an async function which means that the loop probably finishes before any of the promises it created do. To fix this, you need to get the result of your promises and wait on them. However, this means the enclosing function must itself be async, which may or may not be acceptable for your actual use case.
export const getRadioValues = async (record: IRecordInput) => {
const singleSelectKeys = ['Race', 'DeathWas', 'MannerOfDeath'];
await Promise.all(singleSelectKeys.map(async key => {
if (record[key]) {
const dropDownOption = await DropDownOptions.find({ where: { id: record[key] }}) as IPDFSelect;
record[key] = dropDownOption.dataValues.Text;
console.log(record[key]);
}
}));
console.log(record);
return record;
};
Related
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 11 months ago.
I have an api route that needs to take data from two sources, merge the data together into one object and then return. The issue I am having is I'm basically stuck in async/await hell and when pushing to a second array within the .then() block, the second array named
clone returns []. How can I make an api request, merge the data and return to the requester as needed?
Fetch code:
export default async function getProduct(product_id) {
const product = await fetch(
`${process.env.PRIVATE_APP_URL}/products/${product_id}.json`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then((result) => {
return result.json();
});
return product.product;
}
API Handler:
const recharge_subscription_res = await rechargeAPI(
"GET",
`https://api.rechargeapps.com/subscriptions?customer_id=${recharge_customer.id}`
);
const closest_array = recharge_subscription_res.subscriptions.filter(
(e) => e.next_charge_scheduled_at == closest_date
);
let clone = [];
closest_array.forEach((element) => {
getProduct(element.shopify_product_id).then((product) => {
element.shopify_product_handle = product.handle;
element.shopify_product_image_url = product.image.src;
clone.push(element);
});
});
console.log(clone);
clone should log as an array of objects like closest_array, but instead logs as just an empty array. This isn't exactly like the other seemingly duplicate questions because typically their feature doesn't require sending the promise's data back to an external source. Most questions are related to the front end of things. My situation is with an Express.js API. Any help would be appreciated.
The original promise spec used .then(), and the new syntax hides then's with await. Style-wise, it makes sense to choose just one style and go with it.
In either style, there's a little challenge having to do with creating many promises in a loop. The js iteration functions (like map and forEach) take synchronous functions. The most common design is to create a collection of promises in a synchronous loop, then run them concurrently with Promise.all(). Taking both ideas into account...
You could (but don't have to) rewrite your network request like this...
// since we decorated "async" let's use await...
export default async function getProduct(product_id) {
const url = `${process.env.PRIVATE_APP_URL}/products/${product_id}.json`;
const options = { method: "GET", headers: { "Content-Type": "application/json" }};
const result = await fetch(url, options);
const product = await result.json();
return product.product;
}
await is not permitted at the top-level; it may be used only within an async function. Here, I'll make up a name and guess about the parameter
async function rechargeAndLookupProduct(recharge_customer) {
const base = 'https://api.rechargeapps.com/subscriptions';
const query = `customer_id=${recharge_customer.id}`;
const recharge_subscription_res = await rechargeAPI("GET",`${base}?${query}`);
const closest_array = recharge_subscription_res.subscriptions.filter(e =>
e.next_charge_scheduled_at == closest_date
);
// here's the important part: collect promises synchronously
// execute them together with Promise.all()
const promises = closest_array.map(element => {
return getProduct(element.shopify_product_id)
});
const allProducts = await Promise.all(promises);
// allProducts will be an array of objects that the promises resolved to
const clones = allProducts.map((product, i) => {
// use Object.assign so we'll really have a "clone"
let closest = Object.assign({}, closest_array[i]);
closest.shopify_product_handle = product.handle;
closest.shopify_product_image_url = product.image.src;
return closest;
});
// if I didn't make any typos (which I probably did), then
// clones ought to contain the result you expect
console.log(clones);
}
Your code has a flaw (in the section shown below). You have a dangling promise that you forgot to await or return.
When you log clone, none of the async getProduct operations have completed yet, and none of the elements have been pushed.
let clone = [];
closest_array.forEach((element) => {
getProduct(element.shopify_product_id).then((product) => {
element.shopify_product_handle = product.handle;
element.shopify_product_image_url = product.image.src;
clone.push(element);
}); // FLAW: dangling .then
});
console.log(clone); // FLAW: clone is not ready yet.
I would set it up more like this:
let clone = await Promise.all(closest_array.map((element) =>
getProduct(element.shopify_product_id).then((product) => {
element.shopify_product_handle = product.handle;
element.shopify_product_image_url = product.image.src;
return element;
})
));
console.log(clone);
It's a little sketchy to modify element the way you are (I wouldn't), but this way the getProduct calls are all in flight together for maximum efficiency. Promise.all handles awaiting all the promises and putting each's result into an array of results, which you can then await as a single promise since the calling function is async.
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 2 years ago.
The below JavaScript is returning a strange version of an array. The below forEach logs nothing to console. I'm probably missing something quite fundamental here, but I know for a fact that the array is populated...unless it's not populated until later due to being async and Chrome just evaluates the console.log after the fact?
let damageRollStringArr = []
monster.damage_types.forEach(async (damageTypeName) => {
const damageType = await checkResponseAndReturnValue(await DamageTypesService.findDamageType(damageTypeName.trim()))
if (damageType !== null) {
damageRollStringArr.push(damageType.damage)
}
})
damageRollStringArr.forEach(el => console.log(el))
// Was returning an empty array[] with objects inside?
return damageRollStringArr
Thanks
demageRollStringArr.forEach (2nd foreach) wont wait for monster.demage_types.forEach (1st foreach) to execute although there is async await in the first forEach
To achieve what you want to do, try this,
let damageRollStringArr = []
const promises = monster.damage_types.map(async (damageTypeName) => {
const damageType = await checkResponseAndReturnValue(await DamageTypesService.findDamageType(damageTypeName.trim()))
if (damageType !== null) {
damageRollStringArr.push(damageType.damage)
}
})
await Promise.all(promises)
damageRollStringArr.forEach(el => console.log(el))
return damageRollStringArr
Or, you can use normal for() loop to replace 1st foreach
let damageRollStringArr = []
for(let i = 0; i < monster.demage_types.length; i++) {
const damageType = await checkResponseAndReturnValue(await DamageTypesService.findDamageType(damageTypeName.trim()))
if (damageType !== null) {
damageRollStringArr.push(damageType.damage)
}
}
damageRollStringArr.forEach(el => console.log(el))
return damageRollStringArr
1st solution will run the loop parallel,
2nd solution will run the loop sequential
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
How to return an Ajax result using async/await? [duplicate]
(3 answers)
Closed 2 years ago.
I'm not getting the return value I'm expecting from an async function when I try to save the return value in a variable. When I step into my function and inspect the return value with the debugger statement (an array of numbers representing categories i.e. [123, 423, 874, 999, 234, 452]) it is what I expect it to be.
I am using the run() function as that I'm using as a wrapper for when I call the getRandomCategories() function. When I console.log(res) it is an array of ids (this is what I'm expecting)
But when I try to save the return value in a variable (const categoriesArray = run()) I'm expecting an array of ids so I can use the array for another function instead I'm getting a Promise with a pending state. What am I missing?
Here's my code:
async function getData(endpoint, query, value) {
const res = await axios.get(
`http://jservice.io/api/${endpoint}?&${query}=${value}`
);
return res;
}
// createa a function that will return 6 random categories
async function getRandomCategories() {
try {
const res = await getData('categories', 'count', 50);
const data = res.data;
const categories = filterCategoryData(data); // I'm filtering for category id with clues_count === 5
const categoryIdArr = mapCategoryIds(categories); // an array of just category Ids
const shuffledCategoryIds = shuffle(categoryIdArr);
const apiCallCategoryArray = takeFirstXItems(shuffledCategoryIds, 6);
return apiCallCategoryArray;
} catch (err) {
console.log(err);
}
}
async function run() {
const res = await getRandomCategories()
// console.log(res) logs my expected output
return res // I want it to return an array of numbers.
}
const categoriesArray = run() // I'm expecting and array of ids
console.log(categoriesArray) // Why am I not gettng an array of ids in
//my variable? Instead I get a Promise: state <pending>
Since run() returns a Promise as the questioner discovered, either await for the resolved value or attach a then handler as follow.
run().then(categoriesArray => console.log(categoriesArray));
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I need some help to understand the reason of why after a forEach my const is getting empty. I read and find some questions but none of them help me understand. I believe that is happening because JS is asynchronous, but I can't figure out how to solve myself.
So, the code is very simple I have a NodeJS API that will connect to more then one database, and return all the info. I am using pg-promise to connect to PostgreSQL.
export default class AllInfo {
constructor(databases) {
this.databases = databases;
this.options = {
promiseLib: promise,
};
this.databaseConnection = new Pg(this.options);
}
And after that, the trick method:
getAllInformation() {
const entidades = [];
this.databases.getStringConnection().forEach((db) => {
const connection = this.databaseConnection(db);
connection.any('SELECT * FROM information').then((data) => {
entidades.push(data);
});
connection.$pool.end();
});
return entidades;
}
In this code, my return is always empty ([]) when it is requested.
If I log the const entidades inside the loop, the information is logged successfully. But if I log after the loop and before the return, it is empty.
getAllInformation() {
const entidades = [];
this.databases.getStringConnection().forEach((db) => {
const connection = this.databaseConnection(db);
connection.any('SELECT * FROM information').then((data) => {
entidades.push(data);
console.log(entidades) // here it works
});
connection.$pool.end();
});
return entidades;
}
And if I try to log outside:
getAllInformation() {
const entidades = [];
this.databases.getStringConnection().forEach((db) => {
const connection = this.databaseConnection(db);
connection.any('SELECT * FROM information').then((data) => {
entidades.push(data);
});
connection.$pool.end();
});
console.log(entidades) // here doesn't work
return entidades;
}
Someone can explain why this happens and where I look for the solution?
It is as you think. It is returning an empty array because JS is asynchronous and you are returning the data as if it was synchronous.
You can push the promises of connection.any('SELECT * FROM information') into the array instead of pushing the result, thus, you can wait until all the promises are resolved/rejected to continue.
try this:
function getAllInformation() {
const entidades = [];
var entidadesPromises = [];
this.databases.getStringConnection().forEach((db) => {
const connection = this.databaseConnection(db);
entidadesPromises.push(connection.any('SELECT * FROM information'));
connection.$pool.end();
});
return Promise.all(entidadesPromises).then((data) => {
entidades.push(data);
console.log(entidades) // here it works
return entidades;
});
}
getAllInformation().then(entidades => {
// Entidades will be an array containing the data retrieved from the databases.
});
connection.any() is returning a promise and executes the anonymous function that pushes the data to your array AFTER the promise is resolved. This is why the anonymous function is executed asynchronously. However you could wait for the data to be returned by the any function like this:
let data = await connection.any('SELECT * FROM information');
entidades.push(data);
Currently, I am trying to get the md5 of every value in array. Essentially, I loop over every value and then hash it, as such.
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (var userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
console.log(userHashString.toUpperCase() + "this is the hashed string")
if (userHashString.toUpperCase() === poster){
return console.log("this is the poster")
}
else {
..
}
}
else {
return null
}
})
)}
However, this leads to two problems. The first is that I am receiving the error warning "Don't make functions within a loop". The second problem is that the hashes are all returning the same. Even though every userID is unique, the userHashString is printing out the same value for every user in the console log, as if it is just using the first userID, getting the hash for it, and then printing it out every time.
Update LATEST :
exports.sendNotificationForPost = functions.firestore
.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data()
const watching = value.watchedBy
const poster = value.poster
const postContentNotification = value.post
const refPromises = []
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (let userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
if (userHashString.toUpperCase() === poster){
return null
}
else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload)
}
}
else {
return null
}
})
)}
return Promise.all(refPromises);
});
You have a couple issues going on here. First, you have a non-blocking asynchronous operation inside a loop. You need to fully understand what that means. Your loop runs to completion starting a bunch of non-blocking, asynchronous operations. Then, when the loop finished, one by one your asynchronous operations finish. That is why your loop variable userID is sitting on the wrong value. It's on the terminal value when all your async callbacks get called.
You can see a discussion of the loop variable issue here with several options for addressing that:
Asynchronous Process inside a javascript for loop
Second, you also need a way to know when all your asynchronous operations are done. It's kind of like you sent off 20 carrier pigeons with no idea when they will all bring you back some message (in any random order), so you need a way to know when all of them have come back.
To know when all your async operations are done, there are a bunch of different approaches. The "modern design" and the future of the Javascript language would be to use promises to represent your asynchronous operations and to use Promise.all() to track them, keep the results in order, notify you when they are all done and propagate any error that might occur.
Here's a cleaned-up version of your code:
const crypto = require('crypto');
exports.sendNotificationForPost = functions.firestore.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data();
const watching = value.watchedBy;
const poster = value.poster;
const postContentNotification = value.post;
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
return Promise.all(Object.keys(watching).map(userID => {
return admin.database().ref('notifications/' + userID).once('value').then(snapshot => {
if (snapshot.exists()) {
const userHashString = userHash(userID);
if (userHashString.toUpperCase() === poster) {
// user is same as poster, don't send to them
return {response: null, user: userID, poster: true};
} else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload).then(response => {
return {response, user: userID};
}).catch(err => {
console.log("err in sendToDevice", err);
// if you want further processing to stop if there's a sendToDevice error, then
// uncomment the throw err line and remove the lines after it.
// Otherwise, the error is logged and returned, but then ignored
// so other processing continues
// throw err
// when return value is an object with err property, caller can see
// that that particular sendToDevice failed, can see the userID and the error
return {err, user: userID};
});
}
} else {
return {response: null, user: userID};
}
});
}));
});
Changes:
Move require() out of the loop. No reason to call it multiple times.
Use .map() to collect the array of promises for Promise.all().
Use Object.keys() to get an array of userIDs from the object keys so we can then use .map() on it.
Use .then() with .once().
Log sendToDevice() error.
Use Promise.all() to track when all the promises are done
Make sure all promise return paths return an object with some common properties so the caller can get a full look at what happened for each user
These are not two problems: the warning you get is trying to help you solve the second problem you noticed.
And the problem is: in Javascript, only functions create separate scopes - every function you define inside a loop - uses the same scope. And that means they don't get their own copies of the relevant loop variables, they share a single reference (which, by the time the first promise is resolved, will be equal to the last element of the array).
Just replace for with .forEach.