How do I iterate over a asynchronous function in nodejs stopping depending on callback return?
Example:
suppose I have following function
var fn1 = function(i, callback){
// code here
callfunction(i, function(err, p2){
if(err) throw err;
if(something) return callback(true);
else return callback(false);
});
}
I need to iterate and call for i=1,2,3,... until my callback returns false.
I tried using async whilst, but I do not think it can helps me, because the test function also needs callback, and the function for test and iteratee are same, I cannot call twice.
If I understand your problem correctly. What about some recursion
const maxTries = 10;
// just a stupid counter to run a be able to change return value
let numberOfCalls = 0;
const asyncCall = () => {
if (numberOfCalls === 9) {
return Promise.resolve(false);
}
numberOfCalls += 1;
return Promise.resolve();
};
function iterate(calls = 0) {
if (maxTries === calls) {
return Promise.resolve();
}
return asyncCall().then(value => {
if (value === false) {
return value;
}
return iterate(calls + 1);
});
}
iterate().then(result => {
if (result === undefined) {
//to many tries
}
console.log('result', result);
});
Related
I have an Array that is passed through a forEach loop. Each item is then passed through and is checked for a specific order or characters. If it is true the characters are meant to be removed from the string, then it is supposed to be looped back over with the new string. (Old string minus the checked characters)
My problem is the fact that all the functions are still locally holding the old String (Before the characters were removed). How can I pass the new string into the functions without resetting the value "deleteStringData" currently holds?
let dnaArray = ["ACATATAGACATACGT","AAAAAATACATAGTAGTCGGGTAG","ATACATCGGGTAGCGT"];
dnaStrand = "";
//SORT THROUGH EACH ITEM IN ARRAY
dnaArray.forEach((dnaStrand, index) => {
if (findDna(dnaStrand)) {
console.log("Case #" + index + " " + dnaStrand + ": YES");
}
else {
console.log("Case #" + index + " " + dnaStrand + ": NO");
};
});
function findDna(dnaStrand){
if (findHead(dnaStrand)){
if(findBody(dnaStrand)){
console.log("dna");
return true;
}
}
else {
return false;
}
};
function findHead(dnaStrand){
if (findGlobe(dnaStrand)){
if (findEyeSpots(dnaStrand)) {
return true;
}
}
else{
return false;
}
};
function findBody(dnaStrand){
if (findGlobe(dnaStrand) && findLegs(dnaStrand)) {
return true;
}
else {
return false;
}
};
function findGlobe(dnaStrand){
if(findMatch(dnaStrand, /(A+(TAC|CAT)A)/)){
return true;
}else{
console.log("No Globe");
}
};
function findEyeSpots(dnaStrand){
if(findMatch(dnaStrand, /T(CG*T)*AG/)){
return true;
}else{
console.log("No Eyes");
}
};
function findLegs(dnaStrand){
if(findMatch(dnaStrand, /CG*T/)){
return true;
}else{
console.log("No Legs");
}
};
function findMatch (dnaStrand, regex) {
dnaStrand = String(dnaStrand);
let isMatch = dnaStrand.match(regex);
isMatch = String(isMatch[0]);
//console.log(isMatch);
if (isMatch) {
deleteStringData(dnaStrand, isMatch);
return true;
}
else {
return false;
}
};
function deleteStringData (dnaStrand, string) {
dnaStrand = dnaStrand.replace(string, "");
};
Sorry, but I am sort of busy and don't have the time to read through all your code. Based on your question, I can give you some basic guidance.
make sure your function takes a parameter. If each time the end result is not satisfactory or you just want to repeat it again, do an if statement, and if you're going to repeat it, do yourFunction(endresult) inside. If you worry about lag, consider making your end result a global variable and setting a setTimeout to your function.
If you declare the test functions inside the loop you can act on single instance of the strand in that scope without needing to pass any references around.
You can also use Promises to chain the tests.
This may not be the most efficient method but it is very readable.
(async () => {
const strands = [
'ACATATAGACATACGT',
'AAAAAATACATAGTAGTCGGGTAG',
'ATACATCGGGTAGCGT'
];
const results = [];
async function analyze(strand, index) {
let result = {
'case' : parseInt(index), strand
};
function findMatch(pattern) {
return new Promise((resolve, reject) => {
let m = strand.match(pattern)[0];
if (m) {
strand = strand.replace(m, '');
resolve(true);
} else {
reject();
}
});
}
function findHead() {
return findGlobe().then(findEyes);
}
function findBody() {
return findGlobe().then(findLegs);
}
function findGlobe() {
return findMatch(/(A+(TAC|CAT)A)/);
}
async function findEyes() {
result.eyes = await findMatch(/T(CG*T)*AG/);
}
async function findLegs() {
result.legs = await findMatch(/CG*T/);
}
function found() {
result.dna = true;
}
function failed() {
result.dna = false;
}
function done() {
results.push(result);
}
await Promise.all([
findHead(),
findBody()
]).then(found)
.catch(failed)
.finally(done);
}
for (let i in strands) {
await analyze(strands[i], i);
}
console.log(results);
})();
I have the following function involving mongoose:
let username = userObject.username;
Purchase.find({
account: username,
fufilled: true
})
.populate("keys")
.exec(function(err, foundPurchases) {
if (err) {
return inStockItems;
} else {
if (foundPurchases.length === 0) {
return inStockItems;
} else {
// these lists will be a list of IDs of game detail entries in the database
let listOfReceivedIds = foundPurchases.keys.map(obj => obj.game);
for (let i = 0; i < inStockItems.length; i++) {
if (inStockItems.length <= minimum) {
return inStockItems;
}
let currentProductAnalysing = inStockItems[i];
if (listOfReceivedIds.includes(currentProductAnalysing._id)) {
console.log("removing product");
inStockItems.splice(i, 1);
}
}
return inStockItems;
}
}
});
I am running the function like the following, which returns undefined
inStockItems = function_name(inStockItems, userObject, amount);
How can I rewrite the function so that the function returns the value of inStockItems and not undefined. Thanks.
.exec in Mongoose returns a promise of the query result if you don't give it a callback. Your function here needs to be calling .exec() and changing its return value to return something different from the function like this:
async function function_name(inStockItems, userObject, amount) {
const foundPurchases = await Purchase.find({
account: username,
fufilled: true
})
.populate("keys")
.exec();
if (foundPurchases.length === 0) {
return inStockItems;
} else {
let listOfReceivedIds = foundPurchases.keys.map(obj => obj.game);
for (let i = 0; i < inStockItems.length; i++) {
if (inStockItems.length <= minimum) {
return inStockItems;
}
let currentProductAnalysing = inStockItems[i];
if (listOfReceivedIds.includes(currentProductAnalysing._id)) {
console.log("removing product");
inStockItems.splice(i, 1);
}
}
return inStockItems;
}
}
The other alternative would be to pass a callback parameter to your function and call it from the exec() callback but promises are generally cleaner to work with.
The following code, I cannot find any problem out, but yet, it sometimes output
"'i' should not be higher than handlers.length".
function callNext(req, res, handlers, cb) {
let i = -1;
let next = (thisObj) => {
i += 1;
if (i == handlers.length) {
return cb();
} else if (i > handlers.length) {
console.log("'i' should not be higher than handlers.length.");
console.log(handlers.length, i); // => 9 10
return;
}
try {
return handlers[i].call(thisObj || this, req, res, next);
} catch (e) {
this.onerror(e, req, res);
}
};
return next();
}
Because based on human logic, the function is returned when i is equal to handlers.length, and the code below should never run, this problem really drives me nut.
The easiest assumption is that some of the handlers wrongly call next twice.
Generally i would recommend to safeguard again this anyways, you could use something like
function callNext(req, res, handlers, cb) {
let lastCalledIndex;
let next = (thisObj, index) => {
if (index === lastCalledIndex) {
console.log("'next' called multiple times from handler");
return;
}
lastCalledIndex = index;
if (i === handlers.length) {
return cb();
} else if (index > handlers.length) {
console.log("'index' should not be higher than handlers.length.");
console.log(handlers.length, index); // => 9 10
return;
}
try {
return handlers[index].call(thisObj || this, req, res, next.bind(undefined, thisObj, index + 1));
} catch (e) {
this.onerror(e, req, res);
}
};
return next(this, 0);
}
It happens because i will increment on the next call after if (i == handlers.length) return cb(); and become handlers.length + 1
You have to do
if (i >= handlers.length) {
return cb();
}
or something like this.
I am using a node.js module that has a method without callbacks. Instead of it, has an event that fires when that method has finished. I want resolve a promise, using that event as callback securing me that method has been completed succesfully.
array.lenght on promise can be X. So, I need 'hear' X times the event to secure me that all methods has completed succesfully <-- This is not the problem, I am just telling you that I know this could happen
Event :
tf2.on('craftingComplete', function(recipe, itemsGained){
if(recipe == -1){
console.log('CRAFT FAILED')
}
else{
countOfCraft++;
console.log('Craft completed! Got a new Item #'+itemsGained);
}
})
Promise:
const craftWepsByClass = function(array, heroClass){
return new Promise(function (resolve, reject){
if(array.length < 2){
console.log('Done crafting weps of '+heroClass);
return resolve();
}
else{
for (var i = 0; i < array.length; i+=2) {
tf2.craft([array[i].id, array[i+1].id]); // <--- this is the module method witouth callback
}
return resolve(); // <---- I want resolve this, when all tf2.craft() has been completed. I need 'hear' event many times as array.length
}
})
}
At first lets promisify the crafting:
function craft(elem){
//do whatever
return Promise((resolve,reject) =>
tf2.on('craftingComplete', (recipe,itemsGained) =>
if( recipe !== -1 ){
resolve(recipe, itemsGained);
}else{
reject("unsuccessful");
}
})
);
}
So to craft multiples then, we map our array to promises and use Promise.all:
Promise.all( array.map( craft ) )
.then(_=>"all done!")
If the events will be fired in the same order as the respective craft() calls that caused them, you can use a queue:
var queue = []; // for the tf2 instance
function getNextTf2Event() {
return new Promise(resolve => {
queue.push(resolve);
});
}
tf2.on('craftingComplete', function(recipe, itemsGained) {
var resolve = queue.shift();
if (recipe == -1) {
resolve(Promise.reject(new Error('CRAFT FAILED')));
} else {
resolve(itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
var promises = [];
for (var i = 1; i < array.length; i += 2) {
promises.push(getNextTf2Event().then(itemsGained => {
console.log('Craft completed! Got a new Item #'+itemsGained);
// return itemsGained;
}));
tf2.craft([array[i-1].id, array[i].id]);
}
return Promise.all(promises).then(allItemsGained => {
console.log('Done crafting weps of '+heroClass);
return …;
});
}
If you don't know anything about the order of the events, and there can be multiple concurrent calls to craftWepsByClass, you cannot avoid a global counter (i.e. one linked to the tf2 instance). The downside is that e.g. in two overlapping calls a = craftWepsByClass(…), b = craftWepsByClass() the a promise won't be resolved until all crafting of the second call is completed.
var waiting = []; // for the tf2 instance
var runningCraftings = 0;
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (--runningCraftings == 0) {
for (var resolve of waiting) {
resolve();
}
waiting.length = 0;
}
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
console.log('Craft completed! Got a new Item #'+itemsGained);
}
});
function craftWepsByClass(array, heroClass) {
for (var i = 1; i < array.length; i += 2) {
runningCraftings++;
tf2.craft([array[i-1].id, array[i].id]);
}
return (runningCraftings == 0
? Promise.resolve()
: new Promise(resolve => {
waiting.push(resolve);
})
).then(() => {
console.log('Done crafting weps of '+heroClass);
});
}
Of course in both solutions you must be 100% certain that each call to craft() causes exactly one event.
You can look at the event-as-promise package. It convert events into Promise continuously until you are done with all the event processing.
When combined with async/await, you can write for-loop or while-loop easily with events. For example, we want to process data event until it return null.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
let done;
while (!done) {
const result = await eventAsPromise.upcoming();
// Some code to process the event result
process(result);
// Mark done when no more results
done = !result;
}
emitter.removeListener('data', eventAsPromise.eventListener);
If you are proficient with generator function, it may looks a bit simpler with this.
const eventAsPromise = new EventAsPromise();
emitter.on('data', eventAsPromise.eventListener);
for (let promise of eventAsPromise) {
const result = await promise;
// Some code to process the event result
process(result);
// Stop when no more results
if (!result) {
break;
}
}
emitter.removeListener('data', eventAsPromise.eventListener);
I need check if event 'craftingComplete' has fired many times as I
call tf2.craft. Doesnt matters any posible ID or if craft has failed.
I need to know if tf2.craft has finished and only why is checking
'craftingComplete' event
Given that we know i within for loop will be incremented i += 2 where i is less than .length of array, we can create a variable equal to that number before the for loop and compare i to the number within event handler
const craftWepsByClass = function(array, heroClass) {
return new Promise(function(resolve, reject) {
var countCraft = 0;
var j = 0;
var n = 0;
for (; n < array.length; n += 2);
tf2.on('craftingComplete', function(recipe, itemsGained) {
if (recipe == -1) {
console.log('CRAFT FAILED')
} else {
countOfCraft++;
console.log('Craft completed! Got a new Item #' + itemsGained);
if (j === n) {
resolve(["complete", craftCount])
}
}
})
if (array.length < 2) {
console.log('Done crafting weps of ' + heroClass);
return resolve();
} else {
try {
for (var i = 0; i < array.length; i += 2, j += 2) {
tf2.craft([array[i].id, array[i + 1].id]);
}
} catch (err) {
console.error("catch", err);
throw err
}
}
})
}
craftWepsByClass(array, heroClass)
.then(function(data) {
console.log(data[0], data[1])
})
.catch(function(err) {
console.error(".catch", err)
})
Okay so i know that it is bad pratice to force node.js to be syncronous but in this case i have no choice.
I am trying to create a tree like structure of my categories for this i have created this function:
router.route('/api/categoryStructure')
.get(function (req, res) {
var cat = Category.build();
cat.getRootCategories(function (categories) {
var result = [];
var root = categories;
root.forEach(function (y) {
var tmp = findTree(y);
result.push(tmp);
});
})
});
function findTree(rootCategory) {
var root = [rootCategory];
var result = [];
(function loop() {
var element = root[0];
var category = Category.build();
category.retrieveByParentId(element.id, function (categories) {
if (categories) {
element.dataValues.subcategories = categories;
categories.forEach(function (division) {
root.push(division);
});
root.splice(0, 1);
if (result.length == 0) {
result.push(element);
loop()
}
else if (root.length == 0) {
return result;
}
else {
loop()
}
}
else
{
result = root;
return result;
}
});
}());
}
Now as you can see it loop through each of the root categories to find all subcategories and all of their subcategories.
This works perfectly fine however there is a problem
The tmp variable in my loop is set to undefined because of the asyncronous behavior of node. This means that my array is being filled up with undefined/ null values.
So my question is how can i avoid this?
First solution:
Lets add some logic to findTree to make it accepts callbacks
function findTree(rootCategory, callback) {
var root = [rootCategory];
var result = [];
(function loop() {
var element = root[0];
var category = Category.build();
category.retrieveByParentId(element.id, function (categories) {
if (categories) {
element.dataValues.subcategories = categories;
categories.forEach(function (division) {
root.push(division);
});
root.splice(0, 1);
if (result.length == 0) {
result.push(element);
loop()
}
else if (root.length == 0) {
callback(result);
}
else {
loop()
}
}
else
{
result = root;
callback(result);
}
});
}());
}
then you can now call findTree with a callback having any logic you want to be executed secondly.
findTree(y,function(data){
result.push(data);
});
Another way using async module.
You could use async module . Its auto function is awesome . If you have function A() and function B() and function C() . Both function B() and C() depend of function A() that is using value return from function A() . using async module function you could make sure that function B and C will execute only when function A execution is completed .
Ref : https://github.com/caolan/async
async.auto({
A: functionA(){//code here },
B: ['A',functionB(){//code here }],
C: ['A',functionC(){//code here }],
D: [ 'B','C',functionD(){//code here }]
}, function (err, results) {
//results is an array that contains the results of all the function defined and executed by async module
// if there is an error executing any of the function defined in the async then error will be sent to err and as soon as err will be produced execution of other function will be terminated
}
})
});