adding async values to array using forEach [duplicate] - javascript

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am trying to create a filtered array in an array of objects. I am doing so by running case switch in a forEach and deriving a new array attaching that to a new object and pushing that object to an array stored outside of the foreach. but after running the foreach the length of the external array still shows 0 and the rest of the equation relates to the processing of said array. Its two very large blocks of code so i've tried to redact some.
let updatedDrop = []
drop.forEach(async(tollFree) => {
const zipCodes = Object.values(tollFree)[0].split(",");
let updatedList = []
const {
tracking,
mailList,
} = tollFree;
zips = await Zip.find({
"class": { "$in": zipCodes },
});
zips = zips.map(zip => zip.zip4)
switch (zipCodeSuppress) {
case "keepSelect":
(insert case switch)
break;
}
const distinct = (value, index, self) => {
return self.indexOf(value) === index;
};
updatedList = updatedList.flat()
updatedList = updatedList.filter(distinct)
const combinedCost = unitCost + postageCeiling
const dropItem = {
updatedList,
tracking,
}
updatedDrop.push(dropItem)
//console.log(dropItem)
})
console.log(updatedDrop.length)
let advJobs = [];
let cspJobs = [];
let wbJobs = [];
if (updatedDrop.length > 0){ .....
so until i am able to access the updated asynchronous data the rest of the formula is stalled. How do I do this?

The problem you are facing is that the forEach callback doesn't block the main thread, so at the moment you access the array right after the declaration of the forEach, the callback inside didn't finish executing.
Look at this example
const timer = 2000;
const numbers = [1, 2, 3];
const emptyArray = [];
async function getNumberTwo() {
return new Promise((resolve) => {
setTimeout(() => resolve(2), timer);
});
}
async function withForEach() {
numbers.forEach(async (n) => {
const two = await getNumberTwo();
emptyArray.push(n + two);
});
console.log(emptyArray); // the array is empty here.
setTimeout(() => console.log(emptyArray), numbers.length * timer); // if I log the array after all the time has gone, the array has the numbers.
}
withForEach()
but now if you use for of, or even a normal for I would say
// all the declarations from before..
async function withForOf() {
for (const number of numbers) {
const two = await getNumberTwo();
emptyArray.push(number + two);
}
console.log(emptyArray); // now the array has what you expect it to have
}
withForOf()
So, in conclusion, you can use a normal for or a for of to make it work as you need.

Related

findIndex() with async callback with mongoose.findById() inside always returning 0

I am creating simple queing system with tickets. The ticket is inserted into array in place of first ticket with visitTime greater than one to be inserted. The array contains only ticketIds, which have to be looked up in Mongo database using mongoose findById(ticketId) method before any comparing.
However after writing below code I noticed, that findIndex() method returns always index 0, whatever the data in the array is. What am I missing?
Here is the code:
const ticketToInsertTime = convertTime(ticket.visitTime)
const index = que.activeTickets.findIndex(async (ticketId) => {
const ticketFromArray = await Ticket.findById(ticketId).exec()
const ticketTime = convertTime(ticketFromArray?.visitTime!)
return ticketTime > ticketToInsertTime
})
if (index < 0) {
que.activeTickets.push(ticket._id)
que.save()
} else {
que.activeTickets.splice(index, 0, ticket._id)
que.save()
}
function convertTime(time: string) {
const hour = parseInt(time.split(':')[0])
const minutes = parseInt(time.split(':')[1])
return (hour * 60 + minutes)
}
understanding the findIndex functionality, when findIndex has some return has not null or undefined it treat as true then it return index.
if we take your problem async function return Promise then the promise with void itself a not null or undefined so in that case, it returns the first time of promise has a value with index 0.
for this solution:
create a prototype findIndexAsync custom function to simulate the findIndex function.
apply your matching with your database query function like findById with async and return true same in returning from findIndex method of pure Javascript.
arr.findIndexAsync you can use await inside async function to resolve Promise return by arr.findIndexAsync
Thanks.
// lets assume we have this array to be iterate
const arr = [12, 3, 5, 6, 7]
// we create custom find Index prototype function
Array.prototype.findIndexAsync = async function(callback) {
for (let i in this) {
// this callback simulate same as findIndex function callback
const data = await callback(this[i], +i, this)
// if data has true value then break the callbakc calling an return index
if (data) {
return +i // as index
}
}
return -1
}
// we name function find Index async function
arr.findIndexAsync(async(accu, index) => {
// the same you are calling findById method from mongoose you can apply
const data = await databaseFinById(accu);
if (data) {
return true
}
}).then(x => {
// here you can get the index value
// you can set await and get the value oif index
console.log("find matched in db:with array index: ", x)
})
/**
database terms: not need to apply it is only for demo
*/
async function databaseFinById(id) {
const db = [1, 2, 4, 6, 5, 8, 9]
return db.find(accu => accu === id);
}
I solved my problem with slightly different approach. I first fetch list of all active Tickets, then map over them one by one using for ... of loop to retrieve only data I want and finally I find index of specific object in mapped array, which reflects previous array. Code below if someone needs the solution.
const mapTickets = async () => {
const timeArray = []
for (const ticket of que.activeTickets) {
let singleTicket = await Ticket.findById(ticket).exec()
timeArray.push(singleTicket?.visitTime)
}
return timeArray;
}
const mappedTickets = await mapTickets()
const index = mappedTickets.findIndex((time) =>
convertTime(time!) > ticketToInsertTime
)
if (index < 0) {
que.activeTickets.push(ticket._id)
que.save()
} else {
que.activeTickets.splice(index, 0, ticket._id)
que.save()
}
Instead of for loop, which executes in a series, for parralell approach you could use Promise.all() for mapping.
Maybe not the cleanest way, but ot works for me.

Dynamically add values from different json responses - JS

I am calling an API and getting the total_tweet_count. If the meta tag contains a next_token, I fetch the next API (here I call the corresponding object) with the next_token and push the total_tweet_count to an array. However, if the response doesnt contain a next_token, I stop iterating and just push the total_tweet_count and return the array. For some reason, the code doesnt seem to run. Please help.
The below example should get the next_token from res1 and call res2 and push the tweet_count into the array. Then using the next_token of res2, fetch res3 data and push the count. Since res3 doesnt have a next_token, we end the loop and return the data. This is the expectation. Please advice.
const getData = async () => {
const totalCount = [];
const results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
do {
const results1 = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results1.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
getData().then(res => console.log(res))
The final output I expect is [1,20, 8]
Please advice.
Fiddle Link: https://jsfiddle.net/czmwtj3e/
Your loop condition is constant:
const results = await Promise.resolve(data.res1);
//^^^^^^^^^^^^^
…
do {
…
}
while (results.meta.next_token)
What you are looking for is
const getData = async () => {
const totalCount = [];
let results = await Promise.resolve(data.res1);
totalCount.push(results.meta.total_tweet_count)
while (results.meta.next_token) {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
return totalCount;
}
or
const getData = async () => {
const totalCount = [];
let results = {meta: {next_token: 1}};
do {
results = await Promise.resolve(data[`res${results.meta.next_token}`]);
totalCount.push(results.meta.total_tweet_count)
}
while (results.meta.next_token)
return totalCount;
}
Notice there is no const results1 in either of these, but an assignment to the mutable results variable.
As far as I can tell:
getData() is a wholly synchronous process acting on data therefore does not need to be an asyncFunction.
the required array of tweet counts can be created with a combination of Object.keys() and Array.prototype.reduce().
the .next_token property does not need to be used (unless it has some secret meaning beyond what has been explained in the question).
const getData = () => {
const keys = Object.keys(data); // array ['res1', 'res2', 'res3']
const totalCounts = keys.reduce((arr, key) => { // iterate the `keys` array with Array.prototype.reduce()
arr.push(data[key].meta.total_tweet_count); // push next tweet count onto the array
return arr; // return `arr` to be used in the next iteration
}, []); // start the reduction with empty array
return totalCounts; // [1, 20, 8]
};
In practice, you would:
pass data to the function
avoid intermediate variables
const getData = (data) => {
return Object.keys(data).reduce((arr, key) => {
arr.push(data[key].meta.total_tweet_count);
return arr;
}, []);
};

async/await not returning the same object that's console.log is logging [duplicate]

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));

Node.js can't access var in subfunction [duplicate]

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 2 years ago.
With the following function I am trying to calculate the total price of a checkout. But if I console.log() the variable before it is returned, I get 0. If I console.log() the variable in the findOne() function, I get the correct value.
As database I use MongoDB and "Item" is a model.
function calculateOrderAmount(items) {
var totalPayment=0;
items.forEach((item) => {
Item.findOne( { _id: item } )
.then(item => {
if(item) {
// Item exists
totalPayment = item.price;
console.log(totalPayment);
}
});
});
console.log(totalPayment);
return totalPayment;
}
I'm desperate about it and I don't really know what to look for on the internet. Many thanks for answers in advance.
Item.findOne is an async operation, so in your code you execute:
var totalPayment = 0
items.forEach((item) => {...
console.log(totalPayment)
return totalPayment
other sync code who called calculateOrderAmount
then the callback of Item.findOne is run
You must use a callback sytle or an async function like this:
async function calculateOrderAmount (items) {
// beware: if you have a huge items list, doing so could lead to high memory usage
const itemsPromises = items.map((item) => {
return Item.findOne({ _id: item })
.then(item => {
if(item) {
// Item exists
return item.price;
}
return 0
});
})
const prices = await Promise.all(itemsPromises)
const totalPayment = prices.reduce((a, b) => a + b, 0)
console.log(totalPayment)
return totalPayment
}

Async Await does not work as expected [duplicate]

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;
};

Categories