I have following code snippet:
var array = [1, 2, 3];
var data = 0;
for(var i=0; i<array.length; i++){
asyncFunction(data++);
}
console.log(data);
executeOtherFunction(data);
I am expecting value of data as 3 but I see it as 0 due to asyncFunction. How do I call executeOtherFunction when all the asyncFunction calls are done?
Use async.each:
var async = require('async');
var data = 0;
var array = [ 1, 2, 3 ];
async.each(array, function(item, done) {
asyncFunction(data++, done);
}, function(err) {
if (err) ... // handle error
console.log(data);
executeOtherFunction(data);
});
(assuming that asyncFunction takes two arguments, a number (data) and a callback)
If asyncFunction is implemented like the following:
function asyncFunction(n) {
process.nextTick(function() { /* do some operations */ });
}
Then you'll have no way of knowing when asyncFunction is actually done executing because it's left the callstack. So it'll need to notify when execution is complete.
function asyncFunction(n, callback) {
process.nextTick(function() {
/* do some operations */
callback();
});
}
This is using the simple callback mechanism. If you want to use one of freakishly many modules to have this handled for you, go ahead. But implementing something like with basic callbacks might not be pretty, but isn't difficult.
var array = [1, 2, 3];
var data = 0;
var cntr = 0;
function countnExecute() {
if (++cntr === array.length)
executeOtherFunction(data);
}
for(var i = 0; i < array.length; i++){
asyncFunction(data++, countnExecute);
}
Take a look at this module, I think this is what you're looking for:
https://github.com/caolan/async
Related
I'm trying to read multiple data from database, put them into an array, and deal with the array. The code looks like this:
var array = [];
// first for loop
for (var i = 0; i < 10; i++) {
db.read(i, function(rs) { // read data from database and put it into array
array.push(rs);
}
}
// second for loop
for (int i = 0; i < 10; i++) {
console.log(array[i]);
}
However, this piece of code will not work because the second for loop will execute before the first loop ends. Is there any good solutions? BTW, I've used promise like this:
var array = [];
var promise = new Promise(function(resolve, reject) {
for (var i = 0; i < 10; i++) {
db.read(i, function(rs) { // read data from database and put it into array
array.push(rs);
}
}
resolve(array);
};
promise.then(function(array) {
for (int i = 0; i < 10; i++) {
console.log(array[i]);
}
};
It doesn't work either, it seems that the resolve will not wait until all the db read operations finish. So when will the resolve wait until all the previous code finish?
the best way to complete your requirement use async.
var name = ["shekhar", 'vishal', "param", "abhishek", 'amit'];
for (var i = 0; i < 5; i++) {
var item = name[i];//execute your first loop here
}
async.eachSeries(name, processData, function (err) {
if (err) {
res.json({status: 0, msg: "OOPS! How is this possible?"});
}
res.json("Series Processing Done");
})
function processData(item, callback) {
//execute your second loop here
});
Using promises, the asynchronous block of code should return a promise.
The database read function should return a promise, and can easily be rewritten as
function readFromDatabase(i){
return new Promise(function(resolve, reject){
db.read(i, function(rs) { // read data from database and put it into array
resolve(rs);
}
});
}
You then run your loop and get all the values from the database, await the promises and access the result in the .then() function where you can iterate of them.
//We create an empty array to store our promises
var arrayOfPromises = [];
for (var i = 0; i < 10; i++) {
//We push each promise to the array
arrayOfPromises.push(readFromDatabase(i));
}
//We wait until all promises have
Promise.all(arrayOfPromises)
.then(function(arrayOfResults){
//arrayOfResults is an array with the resolved values from the 'readFromDatabase' function
})
.catch(function(){
//Handle errors
});
I have images model and users model.
every image has a user_id field of a user and I want to get the picture of the user and name, add it to the image object and return an array of images.
When I am trying to add author_image field to ONE image I don't have any errors,
But when I am looping over all the images the app crashes the output is that imagesData is undefined as well as userData.
I tried using promises but again I get an error.
What is the best way I can do that without the undefined error?
router.route('/images/all')
.get(function(req,res){
var response = {};
var imagesData = {};
images.find({}).lean().exec(function(err,data){
// console.log(data);
imagesData = data;
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
users.find(({'_id': imagesData[i].user_id}),function(err,userData){
console.log(userData);
imagesData[i].author_pic = userData[0].profile_image;
});
}
}
res.json(imagesData);
});
});
What you missed out is that find operation is not a synchronous operation. So all your find operation immediately move on to the next line.
Although there are multiple ways to handle such situation, I tend to use promises (Q library).
The code would look like this
var Q = require('q');
var defer = Q.defer();
images.find({}).lean().exec(function (err, data) {
// console.log(data);
imagesData = data;
var promiseArr = [];
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
var innerDefer = Q.defer();
users.find(({'_id': imagesData[i].user_id}), function (err, userData) {
console.log(userData);
defer.resolve(userData[0].profile_image);
});
promiseArr.push(innerDefer);
}
}
Q.all(promiseArr).then(function (results) {
for (var i = 0; i < imagesData.length; i++) {
if (Q.isPromise(results[i])) {
results[i] = results[i].valueOf();
}
imagesData[i].author_pic = results[i];
}
res.json(imagesData);
})
});
In this case I am using the Q.all method which basically waits for all the find to finish, and executes only then.
So let's say I have the following for loop
for(var i = 0; i < array.length; i++){
Model.findOne({ _id = array[i].id}, function(err, found){
//Some stuff
});
}
How do I make this code work? Every time I run it I get array[i] = undefinedbecause the mongo-db query is asynchronous and the loop has already iterated 5 times by the time the first query is even completed. How do I go about tackling this issue and waiting for the query to complete before going on to the next iteration?
This doesn't specifically answer your question, but addresses your problem.
I'd use an $in query and do the filtering all at once. 20 calls to the db is pretty slow compared to 1:
// grab your ids
var arrayIds = myArray.map(function(item) {
return item._id;
});
// find all of them
Model.find({_id: {$in: arrayIds}}, function(error, foundItems) {
if (error) {
// error handle
}
// set up a map of the found ids
var foundItemsMap = {};
foundItems.forEach(function(item) {
foundItemsMap[item._id] = true;
});
// pull out your items that haven't been created yet
var newItems = [];
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
if ( foundItemsMap[arrayItem._id] ) {
// this array item exists in the map of foundIds
// so the item already exists in the database
}
else {
// it doesn't exist, push it into the new array
newItems.push(arrayItem);
}
}
// now you have `newItems`, an array of objects that aren't in the database
});
One of the easiest ways to accomplish something like you want is using promises. You could use the library q to do this:
var Q = require('q');
function fetchOne(id) {
var deferred = Q.defer();
Model.findOne({ _id = id}, function(err, found){
if(err) deferred.reject(err);
else deferred.resolve(found);
});
return deferred.promise;
}
function fetch(ids, action) {
if(ids.length === 0) return;
var id = ids.pop();
fetchOne(id).then(function(model) {
action(model);
fetch(ids, action);
});
}
fetch([1,2,3,4,5], function(model) { /* do something */ });
It is not the most beautiful implementation, but I'm sure you get the picture :)
Not sure if this is the right way, it could be a bit expensive but this how i did it.
I think the trick is to pull all your data and then looking for an id match.
Model.find(function(err, data) {
if (err) //handle it
for (var i=0; i<array.length; i++) {
for (var j=0; ij<data.length; j++) {
if(data[j].id == array[i].id) {
// do something
}
}
}
}
How can i wait for callbacks inside a loop changing a variable, and still using asynchronicity?
The 2nd example is using async, in this case i don't know how to add a 2nd parameter sum to wait, in order to avoid a global var sum. Called like wait(sum,value); with a return value sum
wait is a representation for a complex function, which i use in my real-problem, so it can't be rewritten into "inline"-code and has to stay "function".
Example1:
var _ = require('underscore');
var arr = [1,2,3,4,5,6,7];
var sum = 0;
function wait(item,callback) {
setTimeout(function() {
callback(item);
}, Math.ceil(Math.random()*1000));
}
var done = _.after(arr.length,function(value) {
console.log('sum ='+value);
})
_.each(arr,function(itm) {
wait(itm,function(value) {
console.log('waiting... '+value);
sum = sum + value;
})
// Please wait for the callback
console.log(itm);
done(sum);
});
Example2:
function asyncExample2() {
var async = require('async');
var arr = [1,2,3,4,5,6,7];
function otherWait(item, callback) {
setTimeout(function() {
callback(item); // call this when you're done with whatever you're doing
}, Math.ceil(Math.random()*1000));
}
function wait(item, callback) {
setTimeout(function() {
otherWait(item,function() {
console.log(item);
});
callback(item);
}, Math.ceil(Math.random()*1000));
}
function done() { console.log("sum = "+sum);};
var sum = 0;
async.forEach(arr, wait, done);
}
Desired Call:
sum = wait(sum,item)
The easiest way to do this is putting done in the function wait. It makes done called only after the last callback is executed.
var arr = [1,2,3,4,5,6,7];
var sum = 0;
function wait(item,callback) {
setTimeout(function() {
callback(item);
done(sum);
}, Math.ceil(Math.random()*1000));
}
var done = _.after(arr.length,function(value) {
console.log('sum ='+value);
})
_.each(arr,function(itm) {
wait(itm,function(value) {
console.log('waiting... '+value);
sum = sum + value;
})
// Please wait for the callback
console.log(itm);
//done(sum);
});
Output:
Underscore is entirely synchronous so done(sum) would execute before wait has finished executing.
For asynchronous operation don't use underscore.
Something simple like this should do what you want:
var sum = 0;
var waitNext = function(pos) {
wait(arr[pos], function(value)) {
if(pos < arr.length)
{
console.log('waiting... '+value);
sum += value;
waitNext(pos+1);
}
else
{
done(sum);
}
}
}
waitNext(0);
You could of course avoid using waitNext and just modify wait but this will work if wait is not your code.
Not sure if you'd want sum += value inside the if or just before it since now there's an unnecessary waitNext call you could remove by tweaking the if condition's order.
You can really do a recursion version without global variable.
var arr = [1,2,3,4,5,6,7];
function wait(arr, max, callback, sum, done) {
var item = arr.shift();
setTimeout(function(){
if(item) {
sum[0] = callback(item, sum[0]);
sum[1]++;
}
else
sum[1] === max ? done(sum[0]) : wait(arr,max, callback, sum, done);
}, Math.random()*1000);
item && wait(arr, max,callback, sum, done);
}
function cb(item, acc) {
console.log('waiting....' + item);
return item + acc;
}
function done(sum) {
console.log(sum);
}
wait(arr, arr.length,cb, [0, 0], done);
Output
I am assuming that you are using setTimeout to implement the asynchronus behaviour, instead you can use a library like async which handles much more easily for you.
Ex of using async's each
var async = require('async');
var arr = [1,2,3,4,5,6,7];
var sum = 0;
async.forEach(arr, function(item, cb){
sum = sum + item;
cb();
}, function(err){
console.log(sum);
});
I don't know how node implement its amazing idea. And i have a question when use it.
I have to read four files file1.js file2.js file3.js file4.js and concat them into one big javascript file result.js. It's important to keep their order.
So it's normal for me to use readFileSync instead of readFile.
I know it's a bad solution. Anyone has a good idea to do that?
Q: Is it possible for node.js to read four files at the same time?
Hope someone can explain the principle of node.js and when process.nextTick will be fired.
A: yes it is possible for node to read 4 files at the same time.
My answer would be, it depends on your situation, for reading the files synchronously or asynchronously. If it's configuration data, or the files can be cached, I would suggest just doing it synchronously, it's easy, and it's only done once. So you won't be waiting around very much. Long operations on initialization are typical, and can make things in the long run more efficient. That being said, reading four files in order, asynchronously, so that your program can do other things in the background isn't that hard. I will work on sync and async examples of each and add an edit.
/* jshint node:true*/
var fs = require('fs');
function readFilesSync(fileNames) {
'use strict';
var results = '';
for (var i = 0; i < fileNames.length; i++) {
results += fs.readFileSync(fileNames[i]);
}
return results;
}
function readFiles(fileNames, callback) {
'use strict';
var results = '';
function readFile(index) {
if (index < fileNames.length) {
fs.readFile(fileNames[index], function (err, data) {
results += data;
readFile(index + 1);
});
} else {
callback(results);
}
}
readFile(0);
}
function readAllFilesAtOnce(fileNames, callback) {
'use strict';
var results = {};
var numFiles = fileNames.length;
function callBackWrapper() {
var resultsOrdered = '';
for (var i = 0; i < fileNames.length; i++) {
resultsOrdered += results[fileNames[i]];
}
callback(resultsOrdered);
}
function readFileAsync(fileName) {
fs.readFile(fileName, function (err, data) {
results[fileName] = data;
numFiles--;
if (numFiles === 0) {
callBackWrapper();
}
});
}
for (var i = 0; i < fileNames.length; i++) {
readFileAsync(fileNames[i]);
}
}
function doSomethingWithTheData(data) {
'use strict';
console.log('Results async: ' + data);
}
function doSomethingWithTheData2(data) {
'use strict';
console.log('Results async all at once: ' + data);
}
var fileNamesArray = ['blah.js', 'file.js', 'hello.txt'];
console.log('The results sync: ' + readFilesSync(fileNamesArray));
readFiles(fileNamesArray, doSomethingWithTheData);
readAllFilesAtOnce(fileNamesArray, doSomethingWithTheData2);
EDIT: There I added a method to read all of the files at once.
Process.nextTick does no more than process this function on the next time around the event loop. EX:
process.nextTick(function() {
console.log('never printed out');
});
while(true);
ex 2:
process.nextTick(function() {
console.log('printed last');
});
console.log('printed first');