async parallelLimit runs limit times only once - javascript

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.

Related

Asyc function await stops everything, what am I missing?

I have a function that gets called at the end of each loop, but I'm having trouble with the 2nd time around.
async function getData(auth) {
let sheet = sheetIDGlobal;
let selectedRow = sheetsRow;
let sheets = await google.sheets({ version: 'v4', auth });
console.log("Waiting for Google Sheets reply .....");
return new Promise((resolve, reject) => {
sheets.spreadsheets.values.get({
spreadsheetId: sheet,
range: selectedRow,
}, (err, res) => {
if (err) {
reject(err);
console.log('The API returned an error: ' + err);
}
const rows = res.data.values;
if (rows.length) {
promises = rows.forEach((row) => {
// data here
console.log(data);
})
return Promise.resolve(promises);
} else {
console.log('No data found.');
}
console.log(rows, "<----rows");
resolve(rows);
})
})
}
Later in the script, I have another function that calls getData(), but the code after it doesn't get run.
async function loadAddContentPage() {
await getData(authObj); // this function runs
// nothing below this line is run
const createNewPage = 'url-to-get'
driver.get(createNewPage);
}
I know something is wrong with my getData() function, and I assume it's something to do with how I'm both returning a promise and also using resolve.
Just needed to remove the return Promise.resolve(promises);

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

problems with asynchronous data recording

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

Wrap a resultset callback function with a generator/iterator

I'm working on converting a legacy callback-based API into an async library. But I just can't wrap my head around getting a "resultset" to work as a generator (Node 10.x).
The original API works like this:
api.prepare((err, rs) => {
rs.fetchRows(
(err, row) => {
// this callback is called as many times as rows exist
console.log("here's a row:", row);
},
() => {
console.log("we're done, data exausted");
}
);
});
But here is how I want to use it:
const wrapped = new ApiWrapper(api);
const rs = await wrapped.prepare({});
for (let row of rs.rows()) {
console.log("here's a row:", row);
}
let row;
while(row = await rs.next()) {
console.log("here's a row:", row);
}
I thought I had it under control with generators, but it looks like you cannot use yield inside a callback. It actually seems logical if you think about.
class ApiWrapper {
constructor(api) {
this.api = api;
}
prepare() {
return new Promise((resolve, reject) => {
this.api.prepare((err, rs) => {
if (err) {
reject(err);
} else {
resolve(rs);
}
});
});
}
*rows() {
this.api.fetchRows((err, row) => {
if (err) {
throw err;
} else {
yield row; // nope, not allowed here
}
});
}
next() { ... }
}
So what alternatives do I have?
Important: I don't want to store anything in an array then iterate that, we're talking giga-loads of row data here.
Edit
I'm able to simulate the behavior I want using stream.Readable but it warns me that it's an experimental feature. Here's a simplified array-based version of the issue I'm trying to solve using stream:
const stream = require('stream');
function gen(){
const s = new stream.Readable({
objectMode: true,
read(){
[11, 22, 33].forEach(row => {
this.push({ value: row });
});
this.push(null)
}
});
return s;
}
for await (let row of gen()) {
console.log(row);
}
// { value: 11 }
// { value: 22 }
// { value: 33 }
(node:97157) ExperimentalWarning: Readable[Symbol.asyncIterator] is an experimental feature. This feature could change at any time
I finally realized I needed something similar to Go's channels that were async/await compatible. Basically the answer is to synchronize an async iterator and a callback, making them wait for each other as next() iterations are consumed.
The best (Node) native solution I found was to use a stream as an iterator, which is supported in Node 10.x but tagged experimental. I also tried to implement it with the p-defer NPM module, but that turned out to be more involved than I expected. Finally ran across the https://www.npmjs.com/package/#nodeguy/channel module, which was exactly what I needed:
const Channel = require('#nodeguy/channel');
class ApiWrapper {
// ...
rows() {
const channel = new Channel();
const iter = {
[Symbol.asyncIterator]() {
return this;
},
async next() {
const val = await channel.shift();
if (val === undefined) {
return { done: true };
} else {
return { done: false, value: val };
}
}
};
this.api.fetchRows(async (err, row) => {
await channel.push(row);
}).then(() => channel.close());
return iter;
}
}
// then later
for await (let row of rs.rows()) {
console.log(row)
}
Note how each iterating function core, next() and rows(), have a await that will throttle how much data can be pushed across the channel, otherwise the producing callback could end up pushing data uncontrollably into the channel queue. The idea is that the callback should wait for data to be consumed by the iterator next() before pushing more.
Here's a more self-contained example:
const Channel = require('#nodeguy/channel');
function iterating() {
const channel = Channel();
const iter = {
[Symbol.asyncIterator]() {
return this;
},
async next() {
console.log('next');
const val = await channel.shift();
if (val === undefined) {
return { done: true };
} else {
return { done: false, value: val };
}
}
};
[11, 22, 33].forEach(async it => {
await channel.push(it);
console.log('pushed', it);
});
console.log('returned');
return iter;
}
(async function main() {
for await (let it of iterating()) {
console.log('got', it);
}
})();
/*
returned
next
pushed 11
got 11
next
pushed 22
got 22
next
pushed 33
got 33
next
*/
Like I said, Streams and/or Promises can be used to implement this, but the Channel module solves some of the complexity that make it more intuitive.
The original question has two nested callback taking async functions
api.prepare((err,res) => ...)
rs.fetchRows((err,res) => ...)
The first one runs the callback only once so just promisifying it as follows is sufficient.
function promisfied(f){
return new Promise((v,x) => f(x,v));
}
However the second function will invoke it's callback multiple times and we wish to generate an async iterable from this function such that we can consume it in a for await of loop.
This is also possible by employing async generators as follows;
async function* rowEmitterGenerator(rs){
let _v, // previous resolve
_x, // previous reject
_row = new Promise((v,x) => (_v = v, _x = x));
rs.fetchRows((err, row) => ( err ? _x(err) : _v(row)
, _row = new Promise((v,x) => (_v = v, _x = x))
));
while(true){
try {
yield _row;
}
catch(e){
console.log(e);
}
}
}
Then putting all together in a top level await context;
const rows = await promisified(api.prepare),
rowEmitter = rowEmitterGenerator(rows);
for await (let row of rowEmitter){
console.log(`Received row: ${row}`);
// do something with the row
}

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