problems with asynchronous data recording - javascript

there is a function code:
function fillingDataInCategory(categories, url) {
let categoriesData = [];
for (let i = 0; i < categories.length; i++) {
conn.query(`SELECT product_id
FROM products
WHERE product_category_id = ${categories[i].id}`, (err, productInCategory) => {
if(err) {
console.log(err);
fs.writeFileSync('api-reports-error-log.txt',
`${fs.readFileSync('api-reports-error-log.txt')}\n${url}: ${err} ${new Date().toLocaleDateString()}`);
} else {
console.log(productInCategory);
categoriesData.push({category: categories[i].id, productInCategory: productInCategory.length});
}
});
}
}
The problem is that an empty categoriesData array is returned due to asynchronous writing.
I haven't worked with asynchrony much, so I'll be happy to get any help.

I don't see a return at the end of your function, but I assume you want to return categoryData
I think that you are currently calling your function like this:
function myFunc() {
// Do stuff
let categoriesData = fillingDataInCategory(categories, "myurl");
console.log(categoriesData);
// Do stuff
}
First of all, I advise you to use forEach instead of for();
I suggest you to use a promise. And as you do multiple query in one function, use Promise.all
If the library you use to make your mysql calls allows it, use promises directly, but if it only allows you to make callbacks, convert that callback to a promise like this:
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory /* You can put your .length here */}); //resolve with your result
}
});
});
}
Here are the code that i wrote for you. I simulate your call do conn.query() with a delay of 1 sec
const fs = require('fs');
/* SIMULATE conn.query async call */
let conn = {
query: (text, callback) => {
setTimeout(callback(null, [1, 2, 3, 4] /* Return an fake array of result of length 4 */), 1000);
}
}
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory.length}); //resolve with your result
}
});
});
}
function fillingDataInCategory(categories, url) {
return new Promise((resolve, reject) => {
let promiseList = [] //Make a array to fill with pending promise
categories.forEach((categorie) => {
promiseList.push(makeRequest(categorie)); // Adding a new pending request with the categorie inside the array
})
resolve(Promise.all(promiseList)); // Promise.all return a promise that take a list of pending promise
});
}
function myFunc() {
fillingDataInCategory([{id: 1}, {id: 2}, {id: 3}], "myurl").then((categorieData) => {
console.log(categorieData); //Now you get the resolve of the Promise.all
//Do your post work
}).catch((err) => { // If one of all the requests of the Promise.all array throw an error, then all requests fails
//Do error stuffs
});
// Do stuff
}
myFunc();
and i get this :
➜ node test-stackoverflow.js
[
{ category: 1, productInCategory: 4 },
{ category: 2, productInCategory: 4 },
{ category: 3, productInCategory: 4 }
]

Related

Resolve is returned before for loop is completed inside a Promise

I am very confused to why the the return resolve({ status: true, data: newIDs }); is called before the for loop has finished.
Code
createPallet: (data) => {
return new Promise(async (resolve, reject) => {
const newIDs = [];
try {
for (const record of data) {
mysqlConnection.beginTransaction();
mysqlConnection.query(
"INSERT INTO ...",
[record.BatchId, record.PalletNumber, record.PrimaryWeightId],
async (error, results) => {
if (error) {
return reject(error);
}
// Create pallet sku record
await PalletSkusService.createPalletSku(
...
);
console.log(results.insertId);
newIDs.push(results.insertId);
}
);
}
return resolve({ status: true, data: newIDs });
} catch (error) {
mysqlConnection.rollback();
return reject(error);
}
});
},
Expectation
I expect the for loop to get all the new inserted id's and store them into newIDs array. Once that is done, I can return a resolve with the data.
However, this part I don't quite understand is - why does the resolve is ran before the for loop has finished?
What is the correct approach in this situation?
mysqlConnection.query is returning immediately and not waiting for the data to return. I don't know what library you're using for this but you want to find the promise based method to accomplish this. Then, modify the loop so it waits for an array of promises to complete before resolving.
async createPallet(data) {
try {
// modified loop
const newIDs = await Promise.all(data.map((record) => {
mysqlConnection.beginTransaction();
// this is a best guess for how the a promise-style query would look
const await result = await mysqlConnection.query(
"INSERT INTO ...",
[record.BatchId, record.PalletNumber, record.PrimaryWeightId]
);
// Create pallet sku record
await PalletSkusService.createPalletSku(
...
);
console.log(results.insertId);
return results.insertId;
}));
return { status: true, data: newIDs };
} catch (error) {
mysqlConnection.rollback();
throw error;
}
},

async parallelLimit runs limit times only once

I am trying to make use of async.parallelLimit to update records in my db. I need to do this in a batches of 10 and each time the total records will be 1000. I am trying to understand how can i make use of this asyn.parallelLimit in my code. I looked at some example interpreation and tried but it is running only 10 times and after giving back 10 responses it does not give me next ones. I am trying to query the db by executing the filter and then sending the records into async.parallelLimit and get response of all those records. For now i am just trying to understand the parallelLimit and its working. Here is my code
const async = require("async");
module.exports = async (server) => {
async function data(server) {
return await resolveData(server);
}
function resolveData(server) {
return new Promise((resolve) => {
let parcel = server.models["Parcel"].find({
order: "createdAt ASC",
include: [
{
relation: "parcelStatuses",
},
{
relation: "customerData",
scope: {
fields: ["firstName", "lastName", "cityId", "customerId"],
},
},
],
limit: 1000,
skip: 200,
});
resolve(parcel);
});
}
// console.log(await data(server));
var parcelData = await data(server);
for (var i = 1; i <= parcelData.length; i++) {
parcelData[i - 1] = (function (i) {
return function () {
console.log(i);
};
})(parcelData[i]);
}
async.parallelLimit(parcelData, 10, function (err, results) {
if (results) {
console.log("This is resilt", results);
} else {
console.log(err);
}
});
};
I need my parallelLimit function to just return me the records that my query fetch. I will run the update commands later. Since its returning me only 10 records i want to know what i am doing wrong
I'm not sure what the for loop is supposed to do. I'm guessing you're trying to create functions for each elements of the parcelData. An easy way to do that is just map over the parcelData and return an async function:
let async = require("async");
// Your data is here but I used a dummy array with 100 elements
let parcelData = Array(100).fill(0).map((_, i) => i);
// Just a dummy function to wait
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
// map over array and return an `async` function;
let tasks = parcelData.map(p => {
return async () => {
await delay(1000);
console.log(p);
}
});
// Finally execute the tasks
async.parallelLimit(tasks, 10, (err, result) => {
if (err) console.error('error: ', err)
else console.log('done');
});
You can run the code on repl.it to see it in action. Here's one I made.

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

JavaScript - replace setTimeout with async / await

First, I know this is a common question. I'm trying to get a handle on how to use async / await in place of setTimeouts, but all the examples I see online use a setTimeout to simulate the async. This throws me off when it's a set timeout that I'm trying to replace.
In the function below, I want this.filteredResultsto await the results of an API call before trying to filter those results and assign it to this.filteredResults.
getResults() {
let allResults= airtableQuery.getTable("Transfers"); // API call using imported 'getTable' function
console.log(allResults); // returns full array ▶[] although it's not available for filtering yet.
setTimeout(() => { // I want to replace this timeout
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
}, 250); // random ms that is roughly how long airtableQuery takes for the API call.
},
And the airtableQuery:
getTable(table) {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
}
}
);
return recordsArr;
},
Please make the outer function an async function and then await the results before filtering them.
async function getResults() {
let allResults = await airtableQuery.getTable("Transfers");
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
},
Given that getTable() is not a Promise, await will not do anything. For that reason, we can make getTable() return a Promise which will resolve with recordsArr.
getTable(table) {
return new Promise((resolve, reject) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
reject(err)
}else {
resolve(recordsArr)
}
}
);
})
}
Hope it helps.
i always likes primise,this my code show you
getTable(table) {
return new Promise((res, rej) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
res(recordsArr)
},
function done(err) {
if (err) {
this.$toasted.error(err);
rej(err)
}
}
);
})
}
getResults() {
airtableQuery.getTable("Transfers").then(res => {
let allResults = res
console.log(allResults);
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
});
}

Using While loop inside async function?

I'm trying to use a while loop inside an async function, and I suspect my resolve(); is messing up the loop, but I'm not sure what to do about it. Here's the code I have:
app.get("/meal-plan", async function(req, res) {
var calorieCount = req.query.calorieCount;
var mealInput = parseInt(req.query.mealInput);
var dietInput = req.query.food;
var goalPerMeal = (calorieCount / mealInput);
var whichDiet = "diet." + dietInput;
var breakfastGoal = goalPerMeal;
var breakfastArr = [];
async function getBreakfastArr() {
return new Promise((resolve, reject) => {
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
Food.count(breakfastQuery, function(err, count) {
if (err) {
console.log(err);
} else {
var random = Math.floor(Math.random() * count);
Food.findOne(breakfastQuery).skip(random).exec(
function(err, result) {
if (err) {
console.log(err);
} else {
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
resolve();
}
});
}
})
}
});
}
try {
await getBreakfastArr();
console.log(breakfastArr);
res.render("meal-plan.ejs", { meal: mealInput, calories: calorieCount, diet: dietInput, breakfast: breakfast, lunch: lunch, dinner: dinner, totalCalories: totalCalories});
} catch (e) {
res.json(e);
}
});
The goalPerMeal variable takes a user calorie input and divides it by the number of meals they select on my form. I then set that value to a specific breakfast variable called breakfastGoal. The async function finds a random recipe from my database and adds it to my array, breakfastArr. Once it finds the recipe, it subtracts that recipe's calorie count from the breakfastGoal.
I want this function to run until breakfastGoal is reduced to below 150; however, it doesn't seem to work. If I remove the while loop, the function will successfully find the breakfast item, add it to the array, and subtract its calorie count from breakfastGoal. The only thing breaking it is adding the while loop.
Is this something to do with the resolve(); in the async function, or am I missing something important?
Any help is extremely appreciated.
Do you think something like this will work? I have not tested this. Just an idea.
put the while block inside the else block and call recursively.
async function getBreakfastArr() {
let breakfastQuery = { "breakfast": true, [whichDiet]: true };
return this.getRandomRecipe(breakfastQuery).then((response)=>{
return response; //the response will have your breakfast goal
});
}
async function getRandomRecipe(breakfastQuery) {
return Food.count(breakfastQuery, function (err, count) {
if (err) {
console.log(err);
} else {
var random = Math.floor(Math.random() * count);
Food.findOne(breakfastQuery).skip(random).exec(
function (err, result) {
if (err) {
console.log(err);
} else {
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
while (breakfastGoal >= 150) {
this.getRandomRecipe(breakfastQuery);
}
return Promise.resolve(breakfastGoal);
}
});
}
})
}
The problem is that you create only one promise, but have several asynchronous results you need to wait for in the while loop.
The first time resolve is called, that sole promise is resolved, and that will make the code following the await to execute. But at that time your array is not finished at all. Also, any further calls to resolve will not influence that promise any more: a promise can only be resolved once. The other calls are ignored.
The solution is to make a promise in each iteration of the while loop, and await it.
Change this:
return new Promise((resolve, reject) => {
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
to this:
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
await new Promise((resolve, reject) => {
The code could be improved more, but it is not relevant to your question. For instance, it would be better to make each promise resolve with the value to be added to the array breakfastArr, so that you would have:
breakfastArr.push(await new Promise((resolve, reject) => {
// ...
resolve(result)
// ...
});
And the async function should return that array, so that it does not have to be defined at the top.
The issue here is that you are mixing callback taking functions with promises. The proper way to do this is to wrap only the callback taking functions in promise constructors. The result is some thing like this:
app.get("/meal-plan", async function(req, res) {
var calorieCount = req.query.calorieCount;
var mealInput = parseInt(req.query.mealInput);
var dietInput = req.query.food;
var goalPerMeal = (calorieCount / mealInput);
var whichDiet = "diet." + dietInput;
var breakfastGoal = goalPerMeal;
function count(query) {
Food.count(query, function(err, result) {
if (err) {
reject(error)
} else {
resolve(result)
}
});
}
function findOne(query, count) {
return new Promise((resolve, reject) => {
Food.findOne(query).skip(count).exec(function(err, result) {
if (err) {
reject(error)
} else {
resolve(result)
}
});
});
}
async function getBreakfastArr() {
let breakfastQuery = {
"breakfast": true,
[whichDiet]: true
};
let breakfastArr = [];
while (breakfastGoal >= 150) {
let count = await count(breakfastQuery);
let random = Math.floor(Math.random() * count);
let result = await findOne(breakfastQuery, random);
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
}
return breakfastArr
}
try {
let breakfastArr = await getBreakfastArr();
console.log(breakfastArr);
res.render("meal-plan.ejs", {
meal: mealInput,
calories: calorieCount,
diet: dietInput,
breakfast: breakfast,
lunch: lunch,
dinner: dinner,
totalCalories: totalCalories
});
} catch (e) {
res.json(e);
}
});
Some of the Promise libraries had a function called promisify that would take a function taking a callback as its last argument which took node style (err, result) arguments and produced a function that when called returned a promise. If that is available the wrappers get a lot smaller. for example the Food.count wrapper becomes let count = promisify(Food.count); orlet count = promisify(Food.count.bind(Food));
You are returning a promise from async function which is not required. You can remove async from the function returning promise.
So I suggest removing async from function getBreakfastArr()

Categories