NodeJS - sum async results in recursive function - javascript

Hi there I have the following python recursive function that sum values from all child nodes and I want to port in NodeJS but I have some problem with async calls.
def getTree(parent_id, level=1):
c.execute('select * from users where parent_id=?', (parent_id,))
rows = c.fetchall()
total = 0
for child in children:
total += getAsyncValue(child.id)
total += getTree(child.id, level+1)
return total
I tried to do this but I need probably to chain it with promises because the total count is not available while I loop as I get it from an async function
getTree = function(parent_id, level=1) {
c.all("select * from users where parent_id="+parent_id, function(err, children) {
var total = 0;
children.forEach(function(child) {
total += getAsyncValue(child.id)
total += getTree(child.id, level+1)
});
return total;
});
}

without seeing getAsyncValue I can't provide a complete answer - however
var getAsyncValue = function(id) {
return new Promise((resolve, reject) => {
// resolve some value some how
});
};
// helper to make the getTree function "nicer"
c.allAsync = function(str) {
return new Promise((resolve, reject) =>
this.all(str, (err, children) => {
if (err) {
return reject(err);
}
resolve(children);
})
);
};
var getTree = function(parent_id, level=1) {
return c.allAsync("select * from users where parent_id="+parent_id).then(children =>
Promise.all(children.map(child =>
Promise.all([getAsyncValue(child.id), getTree(child.id, level+1)])
.then(([a, b]) => a + b)
)).then(results => results.reduce((a, b) => a + b))
);
};
I think using async/await, the code can be written as:
var getAsyncValue = async function(id) {
return new Promise((resolve, reject) => {
// resolve some value some how
});
};
// helper to make the getTree function "nicer"
c.allAsync = async function(str) {
return new Promise((resolve, reject) =>
this.all(str, (err, children) => {
if (err) {
return reject(err);
}
resolve(children);
})
);
};
var getTree = async function(parent_id, level=1) {
let children = await c.allAsync("select * from users where parent_id="+parent_id);
let total = 0;
for (let i = 0; i < children.length; i++) {
let child = children[i];
total += await getAsyncValue(child.id);
total += await getTree(child.id, level + 1);
}
return total;
};

Related

Custom function not running asynchronously in Promise.all()

I am trying to run a function 4 times (with different parameters) asynchronously with a Promise.all. This snippet runs, however it will only run each 'loopGridValidate' function in order synchronously. I have replaced the loopGridValidate function with the logic from this https://www.javascripttutorial.net/es6/javascript-promise-all/ tutorial and the Promise.all returns what is expected. I'm not sure what I am missing... The loop function is async, everything is set up correctly in the promise all (I have also split out the new promise's into 'const p1 = new Promise' and have the same effect as the code snippet below)
function validateCells() {
var grid = $("#NonStartUpGrid").data("kendoGrid");
var rows = grid.tbody.find("tr"); //rows from excel sheet
var valid = true;
var errorCount = 0;
var rowSec1 = Math.floor(rows.length * .25)
var rowSec2 = Math.floor(rows.length * .5)
var rowSec3 = Math.floor(rows.length * .75)
Promise.all([new Promise((resolve, reject) => { resolve(loopGridValidate(rows, grid, errorCount, 0, rowSec1, valid)); })
, new Promise((resolve, reject) => { resolve(loopGridValidate(rows, grid, errorCount, rowSec1 + 1, rowSec2, valid)); })
, new Promise((resolve, reject) => { resolve(loopGridValidate(rows, grid, errorCount, rowSec2 + 1, rowSec3, valid)); })
, new Promise((resolve, reject) => { resolve(loopGridValidate(rows, grid, errorCount, rowSec3 + 1, rows.length - 1, valid)); })
]).then(results => { const total = results.reduce((a, b) => a + b, 0) });
return errorCount;
};
async function loopGridValidate(rows, grid, errorCount, begin, end, valid) {
console.log(begin);
for (var i = begin; i <= end; i++) {
var rowModel = grid.dataItem(rows[i]);
if (rowModel) {
console.log(i);
var colCells = $(rows[i]).find("td[role=gridcell]");
for (var j = 0; j < colCells.length; j++) {
//custom logic
}
}
}
scrollToTop(grid)
if (valid) {
$("#SubmitGrid").prop("disabled", "");
}
$("#loading").prop("hidden", "hidden");
return errorCount;
}
Here is a fiddle to show a basic example of what I am talking about that is happening. If you click the button you will see in the console, the functions are ran synchronously, instead of asynchronously(the i values would be mixed up instead of in order) https://jsfiddle.net/tap16fbo/
Promise body is synchronous, try using setTimeout. For more information
function loop(begin, end, results) {
setTimeout(() => {
for (var i = begin; i <= end; i++) {
console.log(i);
}
results();
}, Math.random() * 1000)
}
function BeginPromise() {
const p1 = new Promise((resolve, reject) => {
loop(0, 100, resolve)
});
const p2 = new Promise((resolve, reject) => {
loop(110, 200, resolve)
});
const p3 = new Promise((resolve, reject) => {
loop(210, 300, resolve)
});
return Promise.all([p1, p2, p3]).then(() => console.log('done'))
}

Can I resolve a Promise inside another resolved Promise?

I'm writing a method to iterate through a series of items to find either the next incomplete or the last one that returns a promise.
The item status is fetched by another method that also returns a promise and I'm trying to resolve/reject within the resolve callback of this method.
Simplified version of my attempt below:
export const findNextEntry = (i) => {
let length = entries.length
let entry = entries[i]
entry.isLastEntry = i + 1 >= length
return new Promise((resolve, reject) => {
getEntryStatus(i).then(() => {
if (entry.status.complete) {
if ((i + 1) < length) {
findNextEntry(i + 1)
} else {
reject('no more entries')
}
} else {
resolve('entry set')
}
})
})
And then I call the method:
findNextEntry(entry.number).then((resolve) => {
console.log(resolve)
}, (reject) => {
console.log(reject)
})
The resolve/reject callbacks don't seem to be firing, and I get an error Uncaught (in promise) no more entries.
Probebly you must call reject/resolve for nested iterations
export const findNextEntry = (i) => {
let length = entries.length
let entry = entries[i]
entry.isLastEntry = i + 1 >= length
return new Promise((resolve, reject) => {
getEntryStatus(i).then(() => {
if (entry.status.complete) {
if ((i + 1) < length) {
findNextEntry(i + 1).then(resolve).catch(reject)
} else {
reject('no more entries')
}
} else {
resolve('entry set')
}
})
})
Similar but more simple code
function test(i = 0) {
console.log(i);
return new Promise((resolve, reject) => {
if (i < 10) test(i + 1).then(resolve).catch(reject);
else resolve('done');
});
}
test();

How to create a new promise?

Well I understand that one would have to return a promise, and not the result of a promise to pass promises around.
However I seem to be unable to implement this, say I have a member method like:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return true;
}
};
return () => {CreateThem(counter, maxNum);};
}
Basically it creates orders in a recursive fashion, terminating after finding this.MaxRequests empty spots and then puts them together in a list under this.orders
Now I called this function by:
initialLoader.CreateNextBatch().then(function (success) {
console.log(success);
console.log("loaded");
initialLoader.initializeBatch();
});
However this fails:
TypeError: initialLoader.CreateNextBatch(...).then is not a function
at LoadExternDatabase...
Why isn't this working? What am I not understanding yet?
Edit: I've also tried to replace the return by a new promise:
return new Promise((resolve, reject) => {
CreateThem(counter, maxRequests);
resolve();
});
However this executes the resolve immediatelly, instead of waiting for CreateThem to complete. Nor directly the function by return CreateThem.bind(this, counter, maxRequests);
You should resolve a promise after the asynchronous operation done.
For example:
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Resolved here!
// Because asynchronous function `setTimeout` ends here
resolve();
}, 1000);
})
}
In your code, if r >= 0, it will find an entry in database again until r less than 0, so it ends at else block:
if (r >= 0) {
// ...
} else {
// ends here
}
Maybe you could change your code to:
CreateNextBatch() {
return new Promise((resolve, reject) => {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
reject(err);
});
} else {
resolve(true);
}
};
});
// return () => {CreateThem(counter, maxNum);};
}
return new Promise((resolve, reject) => {
globalVarKeep = resolve;
globalVarBreak = reject;
});
Before that you would have had some code waiting for a promise or an event that says: -
CreateThem(counter, maxRequests);
globalVarKeep();
or
CreateThem(counter, maxRequests).then(globalVarKeep);
If that's appropriate.
The function initialLoader.CreateNextBatch() returns a function, not a promise, you should do: initialLoader.CreateNextBatch()()
The function CreateThem can return true if this.maxNum >= 0 and a promise of true or false depending on the recursion. But doesn't allways guaranteed return a promise. And () => {CreateThem(counter, maxNum);}; doesn't return anything either. You could try the following:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
//you can return a promise here
return Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return Promise.resolve(true);
}
};
//you are not returning CreateThem here
return () => {return CreateThem(counter, maxNum);};
}

Issue after nesting async.eachOfSeries

I was using nested for loops to get a thing done. I figured out that the HTTP call and the database query are asynchronous and eventually gives undesired output. So I used async.eachOfSeries. The execution gets stopped just after it enters the inner most async.eachOfSeries in the first iteration itself. I suppose I have made some error is structuring the code and in returning the callbacks. I need to know basically two things from you:
What is stopping the execution of the code in the first iteration itself?
Will this approach solve the asynchronicity issue? Or is there any better approach to solve it?
Your help would be appreciated.
async.eachOfSeries(res, function (value, camp, callback) {
let _id = res[camp]._id;
let arr = res[camp].campaignID;
async.eachOfSeries(arr, function (value1, i, callback) {
let users = arr[i].users;
let id = arr[i].id;
let loop = Math.ceil(users / 1000);
let limit = 0,
offset = 0;
for (let j = 0; j < loop; j++) {
if (users > 1000) {
limit = 1000;
users -= limit;
} else {
limit = users;
}
console.log(limit + " limit " + offset + " offset");
var start = Date.now();
while (Date.now() < start + 100) {}
const request = mailjet
.get("messagesentstatistics")
.request({
"CampaignID": id,
"AllMessages": true,
"Limit": limit,
"Offset": offset
})
request
.then((result) => {
let data = result.body.Data;
var loop = 0;
async.eachOfSeries(data, function (value2, val, callback) {
console.log("ayyuuuuu");
let jsonObj = data[val];
let email = jsonObj.ToEmail;
jsonObj['retailer'] = res[camp].retailer;
jsonObj['summary'] = 'f';
let tempObj = {};
tempObj[id] = jsonObj;
let options = {
new: true
};
let campId = id;
User.addCampaignResponse(email, campId, tempObj, options, function (err, results) {
if (err) {
throw err;
} else {
Campaign.updateResponse(_id, function (err, results2) {
if (err)
throw err;
}) // console.log(results);
}
})
}, function (err) {
callback();
})
})
.catch((err) => {
console.log(err);
})
offset += limit;
}
}, function (err) {
callback();
})
}, function (err) {
callback(undefined, "doneeeeee");
})

Javascript Coding Challenge with setTimeout/Asynchronous Output

I am trying solve the following challenge where I have to write a function triggerActions that passes a callback into the processAction, and produces the output:
"Process Action 1"
"Process Action 2"
...
"Process Action n"
Here is the provided function:
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
Function to code:
function triggerActions(count) {
}
Note that the code for processAction cannot be altered. I was thinking of using a Promise but I'm not sure how. I believe the setTimeout is actually synchronous so I don't know if async/await would work.
My attempt:
triggerActions = count => {
let promises = [];
for(let i=1; i<=count; i++) {
promises.push(new Promise( (resolve, reject) => processAction(i, str => resolve(str))));
}
let results = []
promises.forEach( promise => Promise.resolve(promise).then( async res => results.push(await res)));
return results;
}
I kind of like short and sweet:
var n = 5
var stop = 1
triggerActions = function(text) {
if (text) console.log(text)
if (stop <= n){
processAction(stop++, triggerActions)
}
}
triggerActions()
P.S
It occurred to me that perhaps you are only allowed to provide a function which means the stop variable declaration outside the function is a problem. It makes it a little more verbose, but you can wrap it all inside the function like this:
function triggerActions(stop) {
var rFn = (text) => {
if (text) console.log(text)
if (stop <= n){
processAction(stop++, rFn)
}
}
rFn()
}
triggerActions(1)
There you go:
// Your unaltered function
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
// The function you want to implement
function triggerActions(count) {
var triggerAction = function (i) { // Local function to process the given action number:
if (i <= count) { // More actions to execute?
processAction(i, function (text) {// Process current action number and pass a callback in parameter
console.log(text); // Write the result of processAction
triggerAction(i + 1); // Trigger the next action
}); //
} //
}
triggerAction(1); // First things first: start at action one
}
// Call the function
triggerActions(10);
The original poster's instinct to use promises was correct.
The two solutions above may work but because each call to triggerActions() has to wait for the delay to elapse before the next call can be made, this is considerably slow.
Maybe this is what you want but here's an optimized solution using promises and Promise.all():
const processAction = (i, callback) => {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
const triggerActions = (n) => {
const promises = [];
const generatePromise = (i) => {
return new Promise((resolve, reject) => {
processAction(i, resolve);
});
}
for (let i = 1; i <= n; i += 1) {
promises.push(generatePromise(i));
}
Promise.all(promises)
.then((strings) => strings.forEach((string) => console.log(string)));
}
triggerActions(10);
To compare the performance differences, try running the two approaches side by side.
Here's my solution:
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
// Function to code:
function triggerActions(count) {
const asyncArr = [];
for (let i = 1; i <= count; i++) {
asyncArr.push(new Promise(resolve => processAction(i, resolve)));
}
Promise.all(asyncArr).then((vals) => {
vals.forEach((val) => console.log(val))
});
}
triggerActions(5);
Here is my solution using Promise.all:
function triggerActions(count) {
const promises = range(count).map(
i => new Promise(resolve => processAction(i, resolve))
);
Promise.all(promises).then(results => {
results.forEach(result => console.log(result));
});
}
// Generates an array from 1...n
function range(n) {
return Array.from({ length: n }, (_, i) => i + 1);
}
The requirements are that the function ‘processAction’ should remain unchanged and invoked in a batch.
For this I have used the util.promisify function that takes a function and converts it into a promise. A promise can be invoked in a batch with Promise.all.
Another requirement is that the callback should output “Processed Action i” where i is a number. The anonymous function ‘func’ has been defined to do this.
The triggerActions function takes a number, x, creates an array of numbers containing indices from 0 to x and then invokes a count of x asynchronous functions simultaneously.
const {promisify} = require('util');
function processAction(i, callback) {
setTimeout(function() {
callback("Processed Action " + i);
}, Math.random()*1000);
}
const func = (param1) => console.log(param1);
const promisifyedProcessAction = promisify(processAction);
async function triggerActions(count) {
const arr = [];
for(let i = 0; i < count;)
arr.push(++i);
await Promise.all(
arr.map((value) => promisifyedProcessAction(value,func)));
}
triggerActions(5);
Here's an overview of all the possible approaches:
Callback-based:
Sequential:
function triggerActions(count) {
;(function recur(i = 0) {
processAction(i, (data) => {
console.log(data)
if (i < count) {
recur(i + 1)
}
})
})()
}
Concurrent
function triggerActions(count) {
const data = Array.from({ length: count })
for (let i = 0; i < count; i++) {
processAction(i, (result) => {
data[i] = result
count--
if (count == 0) {
for (const x of data) {
console.log(x)
}
}
})
}
}
Promise-based:
We can use this function to make processAction async:
function processActionP(i) {
return new Promise((res) => processAction(i, res))
}
Sequential:
async function triggerActions(count) {
for (let i = 0; i < count; i++) {
const data = await processActionP(i)
console.log(data)
}
}
Concurrent:
async function triggerActions(count) {
const data = await Promise.all(
Array.from({ length: count }, (_, i) => processActionP(i)),
)
for (const x of data) {
console.log(x)
}
}
Concurrent, using lodash/fp
const _ = require('lodash/fp')
const triggerActions = _.pipe(
_.range(0),
_.map(processActionP),
Promise.all.bind(Promise),
data => data.then(
_.each(console.log)
),
)

Categories

Resources