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)
),
)
Related
i have a piece of code like this:
for(let j = 0; j < someNumber; j++) {
const forkedChildOne = fork("./child.js");
const childOne = await new Promise((resolve, reject) => {
forkedChildOne.on('message', (msg){
// someCode
resolve(msg);
});
});
}
for(let i = 0; i < someAnotherNumber; i++) {
const forkedChildTwo = fork("./child_numberTwo.js");
const childTwo = await new Promise((resolve, reject) => {
forkedChildTwo.on('message', (msg){
// someCode
resolve(msg);
});
});
}
but here, first it wait for the first loop to complete then it goes to the second loop. but i need to run them in parallel. how can i do that? thanks.
Put them in separate functions defined with the async keyword. Then call both functions, one after the other.
const forkloop = async (number, js) {
for(let j = 0; j < number; j++) {
const forked = fork(js);
const child = await new Promise((resolve, reject) => {
forked.on('message', (msg){
// someCode
resolve(msg);
});
});
}
}
const init = async () => {
/* Do not await here */ forkloop(someNumber, './child.js');
/* Do not await here */ forkloop(someAnotherNumber, './child_numberTwo.js');
}
init();
Can you please try using Promise.all ? Please try below code. I have not tested it,, but I believe it should work.
let arrayRequests = [
forkedChildOne.on('message'),
forkedChildTwo.on('message') ];
return Promise.all(arrayRequests).then(results => {
let resultForkedChildOne = results[0];
let resultForkedChildTwo = results[1];
});
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 use NodeJS Async (https://caolan.github.io/async/) as normal for-cycle?
for(var i = 0; i < 100; i++){
doSomething();
}
I need serial flow but normal for-cycle not waiting until action is finished.
I am open to ES6 solutions, if any.
You can try to use Async/Await from ES6 which is cleaner and common shared in many standards. And in addition you don't need any dependencies from third party
const forLoopiNSeria = async () => {
for(var i = 0; i < 10; i++){
console.log(await doSomething(i))
}
}
function doSomething(index) {
return new Promise( (resolve, reject) => {
setInterval(() => resolve(index), 500)
})
}
forLoopiNSeria()
I believe you could always do something like this:
function asyncWhile(condition, action, ctx) {
const whilst = function(data) {
return condition.call(ctx, data) ?
Promise.resolve(action.call(ctx, data)).then(whilst) :
data;
}
return whilst();
}
let i = 1
asyncWhile(
() => {
if (i <= 100) {
i += 1
return true
}
return false
},
() => {
console.log(`iteration ${i}`)
},
)
I found another way with usage of until function from async:
var i = 0;
var max = 100;
async.until(() => {
i++;
return i === max-1;
}, function (callback) {
//doMagic()
callback();
}, function () {
console.log("Finished");
});
Suppose I have two asynchronous functions, A and B, which are independent to each other.
What I am trying to do is that execute these functions sequentially multiple time as shown below
A -> B -> A -> B -> A -> ...
B waits until A finishes and vice versa.
The following is what I have done so far and I know it is not going to work the way I want.
function A() {
var promise = new Promise...
...
return promise;
}
function B() {
var promise = new Promise...
...
return promise;
}
for(var i=0; i<200; i++) {
var p1 = A();
p1.then(() => {
var p2 = B();
// ----
}
}
How should I change the code?
You're headed the right way, but you need to keep chaining the thens. You generally start with a pre-resolved promise from Promise.resolve() then add to the chain using then, keeping each new promise:
let p = Promise.resolve();
for (var i=0; i<200; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
Live Example (with 20 instead of 200):
let counterA = 0;
let counterB = 0;
function A() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterA;
console.log("A done (" + counterA + ")");
resolve();
}, 100);
});
return promise;
}
function B() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterB;
console.log("B done (" + counterB + ")");
resolve();
}, 100);
});
return promise;
}
let p = Promise.resolve();
for (var i=0; i<20; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
.as-console-wrapper {
max-height: 100% !important;
}
(In real code, you'd have a .catch as well to handle rejections, of course; or you'd be returning the last promise to other code that would handle them.)
You can chain calls with chained .then() to make sure they are called after the previous ones are done.
let cA = cB = 0;
function A() {
return new Promise(resolve => {
setTimeout(() => {
resolve("A " + ++cA);
console.log("A done");
}, 200);
});
}
function B() {
return new Promise(resolve => {
setTimeout(() => {
resolve("B " + ++cB);
console.log("B done");
}, 300);
});
}
function callConsecutive(times) {
if (times == 0) return Promise.resolve([]);
times--;
const a = A(),
b = a.then(B),
c = b.then(() => { return callConsecutive(times) });
return Promise.all([a, b, c]).then(([r1,r2,r3]) => Promise.resolve([r1,r2,...r3]));
}
callConsecutive(5).then(results => { console.log(results); })
You can do it with a recursive function
function seqAB(count) {
if(!count) Promise.resolve();
var p1 = A();
p1.then(() => {
var p2 = B();
p2.then(() => {
seqAB(count - 1);
})
})
}
seqAB(200);
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;
};