I'm new to js.
I am using express for node js, and mongoose as a mongo orm.
function direct_tags_search_in_db(tags){
var final_results = [];
for (var i=0; i<tags.length; ++i) {
var tag = tags[i];
Question.find({tags: tag}).exec(function(err, questions) {
final_results.push(questions);
if (i == tags.length -1 ){
return final_results;
}
});
}
};
I get empty results, because of the asynchronously of the find. But I don't know what the best approach for this.
Appriciate a little help, thanks.
You will often find that methods such as Question.find().exec that accept a function as an argument are async. It is especially common for methods that perform network requests or file system operations. These are most commonly referred to as a callback. That being the case, if you would like something to occur when the async task(s) complete, you need to also implement a callback.
Also, it is possible that your reference to tag is being changed in a way that is likely undesired. There are a number of solutions, here is a simple one.
function direct_tags_search_in_db(tags, callback){
var final_results = [];
// Array.forEach is able to retain the appropriate `tag` reference
tags.forEach(function(tag){
Question.find({tags: tag}).exec(function(err, questions) {
// We should be making sure to handle errors
if (err) {
// Return errors to the requester
callback(err);
} else {
final_results.push(questions);
if (i == tags.length -1 ){
// All done, return the results
callback(null, final_results);
}
}
});
});
};
You will notice that when we implement our own callback, that we follow the same common pattern as the callback for Question.find().exec(function(err, result){}); -- first argument a potential error, second argument the result. That is why when we return the results, we provide null as the first argument callback(null, final_results);
Quick example of calling this function:
direct_tags_search_in_db([1, 2, 3], function(err, results){
if (err) {
console.error('Error!');
console.error(err);
} else {
console.log('Final results');
console.log(results);
}
});
Another option for solving various async goals is the async module, promises, or otherwise.
Related
I am listing all files from all directories in /home/myComputer/Desktop/Research, and then filtering them with an if statement to only get the .txt files that I would like to read and store into arrays. All works fine, but pushing the data into the arrays is not functioning. When I console log them, they return no value [].
I tried promise as well as call back function, but they didn't work for me because I didn't know how to implement them properly.
app.get('/jsonData', function(req, res) {
/* Define Arrays */
var theFile = [];
var theCategory = [];
var theContent = [];
var walk = function(dir, done) {
var results = [];
fs.readdir(dir, function(err, list) {
if (err) return done(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, function(err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function(err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
};
//walk(process.env.HOME, function(err, results) {
walk("/home/myComputer/Desktop/Research", function(err, results) {
if (err) throw err;
//console.log(results);
results.map(function(val) {
//Get the filename
var fileName = val.match(/[^\/]+$/).join();
//Get the category
var category = val.substr(48).match(/[^\/]+/);
if (fileName == 'written-speech.txt') {
console.log('FOUND!: ' + fileName + ' Category: ' + category) //this works
fs.readFile(val, 'utf8', function(err, contents) {
console.log(contents); // this works
theFile.push(fileName);
theCategory.push(category);
theContent.push(contents);
});
}
})
});
console.log(theFile); // The problem: This returns an empty Array []
console.log(theCategory); // The problem: This returns an empty Array []
console.log(theContent); // The problem: This returns an empty Array []
});
I expect console.log(theFile); console.log(theCategory); and console.log(theContent); to return the data pushed in them.
The reason for this is that many callbacks in Javascript are asynchronous, which means both fs.readdir and fs.readFile are asynchronous and their callbacks are not called immediately but slightly later (please read about Event Loop in javascript). So at the moment, when you log your arrays they are empty and data to them will be pushed later, e.g. in future. To avoid this you can either use synchronous methods (fs.readdirSync and fs.readFileSync) which is ugly and can cause performance issues if the app has a lot of other asynchronous operations. If in your case it is just a simple script to read some data, it might be fine.
And the other, preferred way is to use promises or some library for managing callbacks, e.g. async. Please read some articles regarding managing async code if these concepts are fully unfamiliar for you, e.g. https://dev.to/mrm8488/from-callbacks-to-fspromises-to-handle-the-file-system-in-nodejs-56p2 to get a basic understanding and see some use case examples.
Regarding your current version, there is no easy way to make it work without a lot of changes. It is better to rewrite it to use the concepts I described earlier.
walk is an asynchronous function because fs.readdir is an asynchronous method and the console.log statements are running (in a synchronous manner) before the callback of fs.readdir getting invoked.
You can console the values of these variables at the end inside the callback of walk.
Maybe this is a general issue, and i need a solution to my case : due to the non blocking aspect of javascript , I don't find how can I execute my function with all iteration in for loop , and here is my example ,
var text_list=[]
for (var i = 0; i < 10; i++) {
var element = array[index];
tesseract.process("img"+i+".jpg", options, function (err, text) {
if (err) {
return console.log("An error occured: ", err);
}
text_list.push(text)
});
}
console.log(text_list) //
And the result as if I do :
tesseract.process("img"+9+".jpg"...
tesseract.process("img"+9+".jpg"...
tesseract.process("img"+9+".jpg"...
.
.
.
and what i need is :
tesseract.process("img"+0+".jpg"...
tesseract.process("img"+1+".jpg"...
tesseract.process("img"+2+".jpg"...
.
.
.
Your question does not really explain what result you are getting and your code looks like it's missing parts of the code. So, all I can really do here to help is to explain generically (using your code where possible) how to solve this class of problem.
If you are ending up with a lot of results that all reference the last value of i in your loop, then you are probably trying to reference i in an async callback but because the callback is called sometime later, the for loop has already finished long before the callback executes. Thus, your value of i is sitting on the last value it would have in the for loop. But, your question doesn't actually show code that does that, so this is just a guess based on the limited result you describe. To solve that type of issue, you have make sure you're separately keeping track of i for each iteration of the loop. There are many ways to do that. In ES6, using let in the for loop definition will solve that entire issue for you. One can also construct a closure, use .forEach(), etc...
Async operations with a loop require extra work and coding to deal with. The modern solution is to convert your async operations to use promises and then use features such as Promise.all() to both tell you when all the async operations are done and to keep the results in order for you.
You can also code it manually without promises. Here's a manual version:
const len = 10;
let text_list = new Array(10);
let doneCnt = 0;
let errFlag = false;
// using let here so each invocation of the loop gets its own value of i
for (let i = 0; i < len; i++) {
tesseract.process("img"+i+".jpg", options, function (err, text) {
if (err) {
console.log("An error occured: ", err);
// make sure err is wrapped in error object
// so you can tell errors in text_list array from values
if (!(err instanceof Error)) {
err = new Error(err);
}
text_list[i] = err;
errFlag = true;
} else {
text_list[i] = text;
}
// see if we're done with all the requests
if (++doneCnt === len) {
if (errFlag) {
// deal with situation where there were some errors
} else {
// put code here to process finished text_list array
}
}
});
}
// you can't process results here because async operations are not
// done yet when code here runs
Or, using promises, you can make a "promisified" version of tesseract.process() and then use promise functionality to track multiple async operations:
// make promisified version of tesseract.process()
tesseract.processP = function(img, options) {
return new Promise(function(resolve, reject) {
tesseract.process(img, options, function(err, text) {
if (err) {
reject(err)
} else {
resolve(text);
}
});
});
}
const len = 10;
let promises = [];
for (let i = 0; i < len; i++) {
promises.push(tesseract.processP("img"+i+".jpg", options));
}
Promise.all(promises).then(function(results) {
// process results array here (in order)
}).catch(function(err) {
// handle error here
});
I understand the concept of callbacks in Javascript. For example this sort of code makes complete sense to me -
function processArray(arr, callback) {
var resultArr = new Array();
for (var i = arr.length-1; i >= 0; i--)
resultArr[i] = callback(arr[i]);
return resultArr;
}
var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]
I understand that callback is just a placeholder function which could be any function that we write later. However there are certain types of callbacks that I have no clue how to write. For example -
function processData (callback) {
fetchData(function (err, data) {
if (err) {
console.log("An error has occured. Abort everything!");
callback(err);
}
data += 1;
callback(data);
});
}
How is the callback to fetchData defined? How does the program know what is err and what is data?
Normally in node.js the structure of a callback is basically like this function(err, result)
This means that the first argument is the error and the second argument is the data. when you have no error you simply pass a null value. This is a standard already.
The second code you posted you should change it like follows:
function processData (callback) {
fetchData(function (err, data) {
if (err) {
console.log("An error has occured. Abort everything!");
callback(err);
}
data += 1;
callback(null, data);
});
}
In the above case, if you did not want to increase the databy 1 you could simply call fetchData(callback).
My two cents :
In JavaScript everything is object and so does function. So functions can be passed as argument to other functions. This enables you to use callback as arguement and then using those callbacks in your api implementation to execute user's task after accomplishing API's task. Here if you would try to re-read your code then you will realize your misconception. Other users have also quoted the same thing here.
EDIT
function fetchdata(callback){
var err = "This is Error message";
var data = {samplekey : "samplevalue"};
callback(err, data); // here in the implementation of fetchdata
// we are making those data and feeding to the callback function.
}
function callback(err, data){
// Implementation of call back
}
Here for making readable purpose I have put callback function separate. You can pass it simply as well.
I think you do not understand JavaScript completely.
I got this example from here
function doSomething(callback) {
// ...
// Call the callback
callback('stuff', 'goes', 'here');
}
function foo(a, b, c) {
// I'm the callback
alert(a + " " + b + " " + c);
}
doSomething(foo);
Trying to find unique ID in Node.js and MongoDB, by creating a while loop that queries MongoDB for existing IDs, until a unique value is found. If the ID is already in use, a number is incremented on the end until Mongo returns nothing.
Everything is working, except for the break; statement when a unique ID is found. Node.js returns: SyntaxError: Illegal break statement
The code:
db.collection('landmarks').findOne({'id':uniqueIDer}, function(err, data){
//if ID exists already
if (data.id){
var uniqueNumber = 1;
while (1) {
var uniqueNum_string = uniqueNumber.toString();
var newUnique = data.id + uniqueNum_string;
db.collection('landmarks').findOne({'id':newUnique}, function(err, data){
if (data.id){
uniqueNumber++;
}
else {
saveLandmark(newUnique);
break;
}
});
}
}
else {
saveLandmark(uniqueIDer);
}
});
What am I doing wrong?
EDIT:
Here's the fixed up code using async if anyone needs it :)
db.collection('landmarks').findOne({'id':uniqueIDer}, function(err, data){
if (data){
var uniqueNumber = 1;
var newUnique;
async.forever(function (next) {
var uniqueNum_string = uniqueNumber.toString();
newUnique = data.id + uniqueNum_string;
db.collection('landmarks').findOne({'id':newUnique,'world':worldVal}, function(err, data){
if (data){
console.log('entry found!');
uniqueNumber++;
next();
}
else {
console.log('entry not found!');
next('unique!'); // This is where the looping is stopped
}
});
},
function () {
saveLandmark(newUnique);
});
}
else {
saveLandmark(uniqueIDer);
}
});
Your break statement is not inside the body of a loop. It is, instead, inside the body of a function, namely the findOne callback. To see this more clearly, it can be helpful to temporarily use a named function as your callback handler:
var cb = function(err, data){
if (data.id){
uniqueNumber++;
}
else {
saveLandmark(newUnique);
break; // not inside a loop!
}
};
db.collection('landmarks').findOne({'id':uniqueIDer}, function(err, data){
//if ID exists already
if (data.id){
var uniqueNumber = 1;
while (1) {
var uniqueNum_string = uniqueNumber.toString();
var newUnique = data.id + uniqueNum_string;
db.collection('landmarks').findOne({'id':newUnique}, cb);
}
}
else {
saveLandmark(uniqueIDer);
}
});
It's pretty clear now that the break in the callback function body is not inside a loop! I've also made things break in other ways because the uniqueNumber and newUnique values are no longer in scope, but that's a different issue. :) The important thing to see here is that a function introduces a "hard" boundary in your code that can be difficult to see based purely on the syntax of the language. This is one of the reasons why this callback style of programming can be so tricky to get right.
In fact, it's much more difficult to do this than your original attempt at the code would imply. You'll need to have a way of passing a success signal up through possibly arbitrary layers of callbacks as you repeatedly call findOne and analyze the result (asynchronously).
You might get some help with this by using the excellent async library, for example https://github.com/caolan/async#whilst.
I have frustrating problem with learning to work with callback style of programming in Node.js. I have a query to a MongoDB database. If I pass in a function to execute on the result it works but I'd rather flatten it out and have it return the value. Any help or direction on how to do this correctly is appreciated. Here's my code:
var getLots = function(response){
db.open(function(err, db){
db.collection('lots', function(err, collection){
collection.find(function(err, cursor){
cursor.toArray(function(err, items){
response(items);
})
})
})
})
}
I want something more like this:
lots = function(){
console.log("Getting lots")
return db.open(openCollection(err, db));
}
openCollection = function(err, db){
console.log("Connected to lots");
return (db.collection('lots',findLots(err, collection))
);
}
findLots = function(err, collection){
console.log("querying 2");
return collection.find(getLots(err, cursor));
}
getLots = function(err, cursor) {
console.log("Getting lots");
return cursor.toArray();
}
Where the final set of data would bubble back up through the function calls.
The problem is that I get an error from Node.js saying that err is not defined or that the collection is not defined. For some reason when I nest the callbacks the correct object is getting passed down. When I try going to this flattened style it complains that things are not defined. I don't know how to get it to pass the necessary objects.
What you need is one of the many control flow libraries available for node via npm and catalogued on the Node.js wiki. My specific recommendation is caolan/async, and you would use the async.waterfall function to accomplish this type of flow where each async operation must be executed in order and each requires the results from the previous operation.
Pseudocode example:
function getLots(db, callback) {
db.collection("lots", callback);
}
function findLots(collection, callback) {
collection.find(callback);
}
function toArray(cursor, callback) {
cursor.toArray(callback);
}
async.waterfall([db.open, getLots, find, toArray], function (err, items) {
//items is the array of results
//Do whatever you need here
response(items);
});
async is a good flow control library. Frame.js offers some specific advantages like better debugging, and better arrangement for synchronous function execution. (though it is not currently in npm like async is)
Here is what it would look like in Frame:
Frame(function(next){
db.open(next);
});
Frame(function(next, err, db){
db.collection('lots', next);
});
Frame(function(next, err, collection){
collection.find(next);
});
Frame(function(next, err, cursor){
cursor.toArray(next);
});
Frame(function(next, err, items){
response(items);
next();
});
Frame.init();