There is a question about for await ... of loop.
I have the following code:
for await (let V of reagentItems.map(objectElement => getPricing(objectElement))) {
console.log(objectElement) //I'd like to have access to the current iteration.
//or
console.log(reagentItems[i]) //current fulfilled element from reagent.map
}
So the problem is that this array (from .map) of functions (V as a single response of getPricing) doesn't return the objectElement itself but I need to have access to this objectElement inside for await ... of loop.
Does it possible somehow or not? And if it doesn't, using of Promise.all handles this problem somehow? Or I should modify the original getPricing and return current objectElement with other results?
From what I can understand from your question and your comments, you want to be able to access both object_element and the result of getPricing(object_element) within the for loop.
Your problem right now is that as a result of map, you only have the result of getPricing, but not the original object_element. As a solution, simply return both from map in an object:
// just a random getPricing function
function getPricing(objectElement) {
return {
item_id: objectElement.id,
price: Math.random()
};
}
const reagent_items = [
{
id: 'a'
},
{
id: 'b'
}
];
for (let V of reagent_items.map(object_element => ({object_element, pricing: getPricing(object_element)}))) {
console.log(`object_element: ${JSON.stringify(V.object_element)}, pricing: ${JSON.stringify(V.pricing)}`);
}
You should not be using for await here at all - that is meant for asynchronous generators. Instead, do
for (const object_element of reagent_items) {
const V = await getPricing(object_element);
console.log(object_element, V)
…
}
With Promise.all, the code would look like this:
Promise.all(reagent_items.map(object_element =>
getPricing(object_element).then(V => {
console.log(object_element, V);
…
})
)).then(…)
// or:
await Promise.all(reagent_items.map(async object_element => {
const V = await getPricing(object_element)
console.log(object_element, V);
…
}));
Related
I am using eslint and getting this error.
Expected to return a value in arrow function
The error is showing on the third line of the code.
useEffect(() => {
let initialPrices = {};
data.map(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
The map function must return a value. If you want to create a new object based on an array you should use the reduce function instead.
const reducer = (accumulator, { category, options }) => (
{...accumulator, [category]:options[0].price}
)
const modifiedData = data.reduce(reducer)
More information https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
The map function is intended to be used when you want to apply some function over every element of the calling array. I think here it's better to use a forEach:
useEffect(() => {
let initialPrices = {};
data.forEach(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
Your map function should return something. Here it's not the case so the error happens. Maybe a reduce function will be more appropriate than map?
From what I can see in your case, is that you want to populate initialPrices, and after that to pass it setSelectedPrice. The map method is not a solution, for you in this case, because this method returns an array.
A safe bet in your case would a for in loop, a forEach, or a reduce function.
const data = [
{
category: "ball",
options: [
{
price: "120.45"
}
]
},
{
category: "t-shirt",
options: [
{
price: "12.45"
}
]
}
];
The forEach example:
let initialPrices = {};
// category and options are destructured from the first parameter of the method
data.forEach(({ category, options}) => {
initialPrices[category] = options[0].price;
});
// in this process I'm using the Clojure concept to add dynamically the properties
setSelectedPrice(initialPrices);
The reduce example:
const initialPrices = Object.values(data).reduce((accumulatorObj, { category, options}) => {
accumulatorObj[category] = options[0].price
return accumulatorObj;
}, {});
setSelectedPrice(initialPrices);
I am calling a recursive function that is returning an object, the object is being returned on each iteration.
I wish to only return an object once the recursive operation has completed. rather than on each iteration.
async fetchRecipe(recipe: any) {
console.log("fetchRecipe");
// Start with a root recipe
let rootRecipe: Recipe = {
id: recipe.id,
name: recipe.name,
ingredients: [],
childRecipes: []
}
// Kick off recursive function
let result = await this.recursivelyBuildRecipe(rootRecipe);
console.log("Fetch Recipe returned");
return result
}
async recursivelyBuildRecipe(recipe: Recipe) {
// fetches using the API
console.log("recursivelyBuildRecipe");
this.fetchChildren('http:///recipes/get_children', 'id=' + recipe.id)
.then(async x => {
await x.data.children.forEach((async(child: { type: any; ItemId: string; name: string; }) => {
switch (child.type) {
case 'ingredient':
// if ingredient
let ingredient: Ingredient = {
id: child.ItemId,
name: child.name,
unit: 1
}
this.allIngredients.push(ingredient);
recipe.ingredients.push(ingredient);
break
case 'recipe':
let subRecipe: Recipe = {
id: child.ItemId,
name: child.name,
ingredients: [],
childRecipes: []
}
await this.recursivelyBuildRecipe(subRecipe)
recipe.childRecipes.push(subRecipe)
break
}
}))
})
// This is returning the same amount of times the recursive function is called, I want it to only return once complete.
var obj = { "recipes": recipe, "ingredients": this.allIngredients }
return await obj;
async recursivelyBuildRecipe(recipe: Recipe) {
const fetch = await this.fetchChildren('http:///recipes/get_children', 'id=' + recipe.id);
const asyncRecipe = await fetch.data.children.reduce(async (accPromise,child) => {
const recipe = await accPromise;
switch(child.type) {
case 'ingredient':
let ingredient: Ingredient = {
id: child.ItemId,
name: child.name,
unit: 1
}
this.allIngredients.push(ingredient);
recipe.ingredients.push(ingredient);
break;
case 'recipe':
let subRecipe: Recipe = {
id: child.ItemId,
name: child.name,
ingredients: [],
childRecipes: []
}
await this.recursivelyBuildRecipe(subRecipe)
recipe.childRecipes.push(subRecipe)
break;
}
return recipe;
},Promise.resolve(recipe));
return { "recipes": asyncRecipe, "ingredients": this.allIngredients }
}
Don't mix Promises and async/await syntax. There's nothing technically incorrect about it, but it's terribly confusing.
You need to iterate over each of the children retrieved and await them. The easiest way to do this, in my opinion, is in a reduce. Although this results in serial retrieval of children - it returns a single object at the end and is easier to reason about. If it's not fast enough, you could do it better with a Promise.all and merge the results yourself.
I'm not sure that the above syntax is 100% correct, but you should be able to get the idea:
I'm not sure I understand specifics here, but it seems what you can do in general is:
Add await for the this.fetchChildren (otherwise it seems like you're getting results because of mutation, not on time).
Add a second boolean parameter to the recursive function (i.e isMainCall), pass it only for the first time (when you start recursion) and add the return in the end into if (isMainCall) return obj
I am very new to Node js and asynchronous programming seems difficult for me to grasp. I am using promise-mysql to make the flow synchronous but I have hit a road block with for loop inside of a chain of promise
I have a multiple choice question module. One table stores all the mcq questions and the other stores all the related choices for the questions. I am using the output of the first query as an input to the second query and so I did promise chaining as below
var mcqAll=[]
var sql_test_q_ans='select qId, q_text from questions'
con.query(sql_test_q_ans)
.then((result)=>{
for(var i=0; i<result.length; i++)
{
ques=result[i]
var sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE
q_id='+result[i].q_id
con.query(sql_test_q_ops)
.then((resultOps)=>{
mcqAll.push({i: ques, ops: resultOps})
console.log(mcqAll)
})
}
})
I am trying to create a javascript object array which would look something like this
[{q_text:'How many states in USA', q_ops:{1:25, 2:35, 3:45, 4:50}}
{question2 and its options}
{question3 and its options}....
]
When I run the above code the object populates all the question's options correctly but the same question is repeated in all the q_text for all questions.
[ { q_text: 'No of states in USA',
[ {op_text: '25', mc_op_id: 113 },
{ op_text: '35', mc_op_id: 114 },
{ op_text: '45', mc_op_id: 115 },
{ op_text: '50', mc_op_id: 116}],
{ q_text: 'No of states in USA',
[ {op_text: 'A', mc_op_id: 1 },
{ op_text: 'B', mc_op_id: 2 },
{ op_text: 'C', mc_op_id: 3 },
{ op_text: 'D', mc_op_id: 4}],
{ q_text: 'No of states in USA',
[ {op_text: 'Yes', mc_op_id: 31 },
{ op_text: 'No', mc_op_id: 32 },
{ op_text: 'No sure', mc_op_id: 33 },
{ op_text: 'Might be', mc_op_id: 34}]
]
I feel like it has something to do with asynchronous flow since console.log before the second query gets printed in all before printing anything after the second query. Any insight would be appreciated
Edit: I added a sample output for better understanding. As seen in the output, the options change and get stored in the js object in the for loop but the question is updated for all the objects to the last question in the for loop
node js current working async and await, still now use to async and await,
use this reference url: https://javascript.info/async-await
async and await is work as promise, await is use to wait to execute script
example
let mcqAll=[]
let sql_test_q_ans='select qId, q_text from questions'
async function showAvatar() {
let result = await con.query(sql_test_q_ans);
if(result.length > 0){
array.forEach((async function (item, index, result) {
let q = result[index];
let sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE
q_id='+result[index].q_id
let executeQuery = await con.query(sql_test_q_ops);
if(executeQuery.affectedRows > 0){
mcqAll.push({index: q, ops: executeQuery})
console.log(mcqAll);
}
});
}
}
You have a scope problem here
This is an example to reproduce your problem:
ques is a global variable that is updated in the for-loop so, when the async code ends the execution will read the global variable with the last ques = result[i] value.
'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []
var ques
for (var i = 0; i < result.length; i++) {
ques = result[i]
var sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + result[i].q_id
query(sql_test_q_ops)
.then(() => {
mcqAll.push({ i: ques })
console.log(mcqAll)
})
}
function query() {
return new Promise(resolve => setTimeout(resolve, 100))
}
But, if you simply declare the ques like this:
for (var i = 0; i < result.length; i++) {
const ques = result[i]
const sql_test_q_op...
all will work.
It is a good practice to use const or let instead of var because the last one creates a global scoped variable that is dangerous.
Regarding your comment: the output is empty because this for-loop is sync, so you reply in sync way to the response.
An example on how to manage this case could be like this:
'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []
const promiseArray = result.map(ques => {
const sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + ques.q_id
return query(sql_test_q_ops)
.then(() => { mcqAll.push({ i: ques }) })
})
// Wait for all the query to complete before rendering the results
Promise.all(promiseArray)
.then(() => {
console.log({ mcqAll });
res.render('mcqAllPage', { mcqAll })
})
.catch(err => res.send(500)) // this is an example
function query() {
return new Promise(resolve => setTimeout(resolve, 100))
}
Consider that there are many possibilities to implement this:
use for async iterator to run query sequentially
improve performance by run only one query with a in condition instead of a query for each q_id and manage the result with some code to group the results
using the promise array as in the example
Go deeper and choose the one that fits best for your need.
Important: .catch always the promise chain!
While learning NodeJS, I've been battling to write a more concise logic to this code block (see below) that could either introduce recursion or make use of ES6 methods to provide more elegance and better readability.
I'm bothered by the nesting happening on the for of loops
Thoughts?
export function pleaseRefactorMe(measures, metrics, stats) {
if (!Array.isArray(metrics)) metrics = [metrics] //=> returns array [ 'temperature' ]
if (!Array.isArray(stats)) stats = [stats] //> returns array [ 'min', 'max', 'average' ]
let statistics = []
/**** refactor opportunity for nested for of loops ****/
for (let metric of metrics) {
for (let stat of stats) {
try {
let value = calculateStatsForMetric(stat, metric, measure)
if (value) {
statistics.push({
metric: metric,
stat: stat,
value: value
})
}
} catch (err) {
return err
}
}
}
return statistics
}
First, always pass arrays in, methods usually shouldn't do this sort of input validation in JavaScript. Also don't throw in calculateStatsForMetric, if you have throwing code there wrap it in a try/catch and return a falsey value.
Now, you can use higher order array methods like flatMap and map:
Take each metric
For each metric
Take each stat (this calls for a flatMap on a map)
Calculate a function on it
Keep truthy values (this calls for a filter)
Or in code:
export const refactored = (measure, metrics, stats) =>
metrics.flatMap(metric => stats.map(stat => ({
metric,
stat,
value: calculateStatsForMetric(stat, metric, measure)
}))).filter(o => o.value);
A simple approach would be to use forEach -
let statistics = [];
metrics.forEach(m => {
stats.forEach(s => {
let value = calculateStatsForMetric(s, m, measures);
if (value) {
statistics.push({
metric: m,
stat: s,
value: value
});
}
});
});
I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}