Hi i am using async module of node.js for implementing a for loop asynchronously.
My question is: how to break the loop execution and get out of the loop? I tried giving return , return false but no luck.
Here is the sample code:
async.until(
function() { return goal; },
function(callback) {
async.each(_rules,
function(rule,callback) {
var outcome = true;
.... some code ....
if(changes){
console.log("hi");
return false;// HERE I NEED TO BREAK
}
else
callback();
},
function(err){ }
);
if (!changes || session.process)
goal = true;
callback();
},
function(err){ callback(session); }
);
async.until repeatedly calls function until the test returns true. So test must return true so that you exit the loop. This is opposite of async.whilst which runs repeatedly while test evaluates to be true.
async.each calls the functions in parallel so what it returns does not matter. It is not a loop which you can break, but an iterator looping over the array. Your condition to stop using async.each should be in test for async.until and you should iterate over the rules yourself.
There isn't really a "loop" as such to break out of. All your items in your collection are used in parallel
The only way to "break" the "loop" is to call the callback with an error argument. As there is nothing to stop you from putting other things in there you could hack it a little bit.
From the docs:
Note, that since this function applies the iterator to each item in
parallel there is no guarantee that the iterator functions will
complete in order.
Even if you return an error, you will still have several outstanding requests potentially so you really want to limit the amount of items you use in one go. To limit the amount of outstanding requests, you could use eachSeries or eachLimit.
For example:
async.each(_rules,
function(rule,callback) {
if(changes){
return callback({ data: 'hi'}); // stop
}
...
if(realerror){
return callback({ error: realerror}); // stop with an error
}
callback(); // continue
},
function(result){
if(!result){
return othercallback('no results');
}
// check if we have a real error:
if(result.error){
return othercallback(result.error);
}
return othercallback(null, result.data);
}
);
PS: if you're not doing async, use underscore
You have also async.detect
Returns the first value in coll that passes an async truth test. The iteratee is applied in parallel, meaning the first iteratee to return true will fire the detect callback with that result.
// asynchronous function that checks if a file exists
function fileExists(file, callback) {
fs.access(file, fs.constants.F_OK, (err) => {
callback(null, !err);
});
}
async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists,
function(err, result) {
console.log(result);
// dir1/file1.txt
// result now equals the first file in the list that exists
}
);
Related
var ing_data = savedata.ingredients.split(',');
for(var i =0; i<ing_data.length; i++){
var d = {
content_name: ing_data[i],
dogFoodId: dogId
}
db.dog_ingredients.create(d).then(function(data){
}, function(e){
console.log(e);
res.status(403).send('Error');
//break for loop this point
});
}
how to break for loop in promise?
I'm using node express, sequelize module
The loop will be over before the first then callback is triggered; this is one of the guarantees of promises (assuming that create operation returns a proper promise, not just a thenable; or at least that the thenable it returns completes asynchronously).
You can use the reduce trick to loop through adding those ingredients serially (one at a time); a promise rejection along the way will skip the remaining ingredients:
savedata.ingredients.split(',').reduce(function(p, ing) {
// Chain this ingredient on the end of the promise, return
// the new promise `then` returns, which gets passed to the
// next iteration
return p.then(function() {
var d = {
content_name: ing,
dogFoodId: dogId
};
// Return the promise from `create`
return db.dog_ingredients.create(d);
});
}, Promise.resolve()/* Seeds the loop above */)
.catch(function(e) {
// We got a rejection, which bypasses any pending resolution
// handlers we set up above; process the rejection.
console.log(e);
res.status(403).send('Error');
return Promise.reject(e); // Only need to propgate the rejection like this
// this if something will use the return value of
// this overall structure
});
That looks massive, but that's mostly comments and the object initializer; we could also write it like this (assuming we didn't need to propagate the rejection):
savedata.ingredients.split(',').reduce(function(p, ing) {
return p.then(function() {
return db.dog_ingredients.create({ content_name: ing, dogFoodId: dogId });
});
}, Promise.resolve())
.catch(function(e) {
res.status(403).send('Error');
});
(Or you can even get smaller, but for me debugging suffers — leave minifying to the minifier.)
I assume you don't want to add the ingredients in parallel since you've indicated you want to stop on the "first" error. But if you did, the code would be simpler:
Promise.all(savedata.ingredients.split(',').map(function(ing) {
return db.dog_ingredients.create({ content_name: ing, dogFoodId: dogId });
}).catch(function(e) {
res.status(403).send('Error');
return Promise.reject(e);
});
(Assumes we don't need to propagate the rejection.)
Again, though, that's parallel.
I've been learning promises using bluebird for two weeks now. I have them mostly understood, but I went to go solve a few related problems and it seems my knowledge has fell apart. I'm trying to do this simple code:
var someGlobal = true;
whilePromsie(function() {
return someGlobal;
}, function(result) { // possibly even use return value of 1st parm?
// keep running this promise code
return new Promise(....).then(....);
});
as a concrete example:
// This is some very contrived functionality, but let's pretend this is
// doing something external: ajax call, db call, filesystem call, etc.
// Simply return a number between 0-999 after a 0-999 millisecond
// fake delay.
function getNextItem() {
return new Promise.delay(Math.random()*1000).then(function() {
Promise.cast(Math.floor(Math.random() * 1000));
});
}
promiseWhile(function() {
// this will never return false in my example so run forever
return getNextItem() !== false;
}, // how to have result == return value of getNextItem()?
function(result) {
result.then(function(x) {
// do some work ...
}).catch(function(err) {
console.warn("A nasty error occured!: ", err);
});
}).then(function(result) {
console.log("The while finally ended!");
});
Now I've done my homework! There is the same question, but geared toward Q.js here:
Correct way to write loops for promise.
But the accepted answers, as well as additional answers:
Are geared toward Q.js or RSVP
The only answer geared toward bluebird uses recursion. These seems like it's likely to cause a huge stack overflow in an infinite loop such as mine? Or at best, be very inefficient and create a very large stack for nothing? If I'm wrong, then fine! Let me know.
Don't allow you to use result of the condition. Although this isn't requirement -- I'm just curious if it's possible. The code I'm writing, one use case needs it, the other doesn't.
Now, there is an answer regarding RSVP that uses this async() method. And what really confuses me is bluebird documents and I even see code for a Promise.async() call in the repository, but I don't see it in my latest copy of bluebird. Is it in the git repository only or something?
It's not 100% clear what you're trying to do, but I'll write an answer that does the following things you mention:
Loops until some condition in your code is met
Allows you to use a delay between loop iterations
Allows you to get and process the final result
Works with Bluebird (I'll code to the ES6 promise standard which will work with Bluebird or native promises)
Does not have stack build-up
First, let's assume you have some async function that returns a promise whose result is used to determine whether to continue looping or not.
function getNextItem() {
return new Promise.delay(Math.random()*1000).then(function() {
return(Math.floor(Math.random() * 1000));
});
}
Now, you want to loop until the value returned meets some condition
function processLoop(delay) {
return new Promise(function(resolve, reject) {
var results = [];
function next() {
getNextItem().then(function(val) {
// add to result array
results.push(val);
if (val < 100) {
// found a val < 100, so be done with the loop
resolve(results);
} else {
// run another iteration of the loop after delay
setTimeout(next, delay);
}
}, reject);
}
// start first iteration of the loop
next();
});
}
processLoop(100).then(function(results) {
// process results here
}, function(err) {
// error here
});
If you wanted to make this more generic so you could pass in the function and comparison, you could do this:
function processLoop(mainFn, compareFn, delay) {
return new Promise(function(resolve, reject) {
var results = [];
function next() {
mainFn().then(function(val) {
// add to result array
results.push(val);
if (compareFn(val))
// found a val < 100, so be done with the loop
resolve(results);
} else {
// run another iteration of the loop after delay
if (delay) {
setTimeout(next, delay);
} else {
next();
}
}
}, reject);
}
// start first iteration of the loop
next();
});
}
processLoop(getNextItem, function(val) {
return val < 100;
}, 100).then(function(results) {
// process results here
}, function(err) {
// error here
});
Your attempts at a structure like this:
return getNextItem() !== false;
Can't work because getNextItem() returns a promise which is always !== false since a promise is an object so that can't work. If you want to test a promise, you have to use .then() to get its value and you have to do the comparson asynchronously so you can't directly return a value like that.
Note: While these implementations use a function that calls itself, this does not cause stack build-up because they call themselves asynchronously. That means the stack has already completely unwound before the function calls itself again, thus there is no stack build-up. This will always be the case from a .then() handler since the Promise specification requires that a .then() handler is not called until the stack has returned to "platform code" which means it has unwound all regular "user code" before calling the .then() handler.
Using async and await in ES7
In ES7, you can use async and await to "pause" a loop. That can make this type of iteration a lot simpler to code. This looks structurally more like a typical synchronous loop. It uses await to wait on promises and because the function is declared async, it always returns a promise:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function processLoop(mainFn, compareFn, timeDelay) {
var results = [];
// loop until condition is met
while (true) {
let val = await mainFn();
results.push(val);
if (compareFn(val)) {
return results;
} else {
if (timeDelay) {
await delay(timeDelay);
}
}
}
}
processLoop(getNextItem, function(val) {
return val < 100;
}, 100).then(function(results) {
// process results here
}, function(err) {
// error here
});
It continues execution even if the condition is true and return res.json() is executed.
_.each(rooms, function(room){
if(room.users.length == users.length) {
return res.json(room); // <-- returns but still continuous execution
}
});
Below is the error printed in console.
error: Error: Cannot write to response more than once
Do I need to do anything else?
As posted in How to break/exit from a each() function in JQuery?, you can exit each loops by returning false. Thus,
_.each(rooms, function(room){
if(room.users.length == users.length) {
res.json(room);
return false;
}
});
If that did not work and you used underscore.js, see how to break the _.each function in underscore.js which recommends using .every where return false works (instead of .each). Thus
_.every(rooms, function(room){
if(room.users.length == users.length) {
res.json(room);
return false;
}
});
you should break the loop and then res.status(200).json(room)
You could consider using a different function in the underscore/lodash api, for example, find would seem to suit what you are doing better:
var room = _.find(rooms, function(room){
return room.users.length == users.length;
});
res.json(room);
Is there a callback for when underscore is finished it's _.each loop because if I console log immediately afterwards obviously the array I am populating with the each loop is not available. This is from a nested _.each loop.
_.each(data.recipe, function(recipeItem) {
var recipeMap = that.get('recipeMap');
recipeMap[recipeItem.id] = { id: recipeItem.id, quantity: recipeItem.quantity };
});
console.log(that.get('recipeMap')); //not ready yet.
The each function in UnderscoreJS is synchronous which wouldn't require a callback when it is finished. One it's done executing the commands immediately following the loop will execute.
If you are performing async operations in your loop, I would recommend using a library that supports async operations within the each function. One possibility is by using AsyncJS.
Here is your loop translated to AsyncJS:
async.each(data.recipe, function(recipeItem, callback) {
var recipeMap = that.get('recipeMap');
recipeMap[recipeItem.id] = { id: recipeItem.id, quantity: recipeItem.quantity };
callback(); // show that no errors happened
}, function(err) {
if(err) {
console.log("There was an error" + err);
} else {
console.log("Loop is done");
}
});
Another option is to build your callback function into the each loop on the last execution:
_.each(collection, function(model) {
if(model.collection.indexOf(model) + 1 == collection.length) {
// Callback goes here
}
});
Edit to add:
I don't know what your input/output data looks like but you might consider using _.map instead, if you're just transforming / rearranging the contents
An array of functions, [fn1,fn2,...], each "returns" through a callback, passing an optional error. If an error is returned through the callback, then subsequent functions in the array should not be called.
// one example function
function fn1( callback ) {
<process>
if( error ) callback( errMsg );
else callback();
return;
}
// go through each function until fn returns error through callback
[fn1,fn2,fn3,...].forEach( function(fn){
<how to do this?>
} );
This can be solved other ways, but nonetheless would love the syntactic dexterity to use approach.
Can this be done?
as per correct answer:
[fn1,fn2,fn3,...].every( function(fn) {
var err;
fn.call( this, function(ferr) { err = ferr; } );
if( err ) {
nonblockAlert( err );
return false;
}
return true;
} );
seems this has room for simplification.
for me, much better approach to solve this type of problem - it's flatter, the logic more accessible.
If I understand your question correctly and if you can use JavaScript 1.6 (e.g. this is for NodeJS), then you could use the every function.
From MDN:
every executes the provided callback function once for each element
present in the array until it finds one where callback returns a false
value. If such an element is found, the every method immediately
returns false. Otherwise, if callback returned a true value for all
elements, every will return true.
So, something like:
[fn1, fn2, fn3, ...].every(function(fn) {
// process
if (error) return false;
return true;
});
Again, this requires JavaScript 1.6