Functionall approach to error handling - javascript

I found this snippet here: http://www.nodewiz.biz/nodejs-error-handling-pattern/
It supposed to handle errors better than normal way in node.js. But I fail to understand how it works and why it is better than plain if(err)?
Functional Approach
An example using the Express.js feramework, define a function that returns an error handler:
function error(res,next) {
return function( err, result ) {
if( err ) {
res.writeHead(500); res.end(); console.log(err);
}
else {
next(result)
}
}
}
Which allows you to do this;
app.get('/foo', function(req,res) {
db.query('sql', error(res, function(result) {
// use result
}));
});
Covering this method only as an example, do not use this method for production code, unless you actually handle the error properly.

"It supposed to handle errors better than normal way in node.js. But I fail to understand how it works..."
How it works is that it receives the res object and a function (provided by you) that gets invoked if the result was successful.
It then returns a function that is passed to db.query() as the callback. So that callback actually receives the err and result.
If it finds an err, it handles sending the appropriate 500 response for you. If it doesn't find an error, it invokes the function you provided passing it the result. That way you know that if the function you provided runs, it was because there was no error.
"...and why it is better than plain if(err)?"
It isn't better. It's just different. It is nice if the error handling will always be the same in that you can update it in one spot, and any code that uses it will benefit from the update.
However, a simple function abstraction could do the same.
function error(res, err) {
if( err ) {
res.writeHead(500); res.end(); console.log(err);
return true
}
return false
}
Which allows you to do this;
app.get('/foo', function(req,res) {
db.query('sql', function(err, result) {
if (error(res, err))
return;
// use the result
}));
});

Related

Wanted help in understanding how parameters are defined in Callback functions, using this example (if possible)

I am trying to understand more on how callback functions work in JavaScript.
I found this simple example code online and after adding the imports & additional txt file it's working. However I am not sure why it is working as the parameters of the functions aren't being defined anywhere?
fs.readFile(`${__dirname}/temp.txt`, (err, data) => {
console.log(`Breed: ${data}`);
superagent.get(`https://dog.ceo/api/breed/${data}/images/random`).end((err,res) =>{
console.log(res.body)
}); })
Upon running this I get the expected result on the console with the line displaying Breed: ${data} and the next line showing the body of the res.
However, the err, data parameters have never been provided to the arrow function within readFile. Neither has the err,res parameters ever been called/provided a value for it to display a message to the console, then how are these lines displaying a message to the console?
I could have understood this working if .readFile returned a err and data value & .end returned a err and res value, however upon reading the documentation both these functions return void, thus i'm confused and help would be appreciated.
You pass a function in to readFile, and later on readFile will call your function. When it calls your function, readFile will pass in the relevant values.
You can do something similar yourself, like this:
function myReadFile(filename, callback) {
setTimeout(() => {
if (Math.random() > 0.5) {
callback(null, 'fake file');
} else {
callback('an error', null);
}
}, 1000);
}
myReadFile(`${__dirname}/temp.txt`, (err, data) => {
console.log(`Breed: ${data}`);
})
(though of course the real readFile function isn't doing timeouts or random numbers, but rather reading from a file and listening for that operation to succeed or error out)

Is there any way to break out of a function by running another function in JS

I am working on a simple project and I would like to create a simple helper function that checks for a error in a callback. If there is a error then it should break the whole function that called it. Code example:
//Makes call to database and tries to insert element
db.collection("data").insertOne(
{
key: 'some-data'
}, (error, result) => {
//Return error if something goes wrong - else error is empty
checkError(error, "Unable to load database");
console.log("Succes item added")
}
);
Note: Yes this is node.js but this whole principle could be repeated in js with other callbacks - very simple repeatable error principle.
So in the insertOne function the first argument is some data I am adding to the database. The second argument is the callback function that is called after this async operation is finished. It returns a error which I could just handle by adding this if statement to the callback:
if (error) {
console.error(error);
return;
}
Buuut thats disrespecting the dry principle (bc I write the exact same if statement everywhere with no syntax being changed except the message) and is also distracting when reading the callback function. Now my issue is in the function checkError() even tho I can just print the error with the message or throw the error, I dont actually have a way to break the original callback so that it doesnt cause any more havoc in my database. I will go on to promisify this callback which is a solution. BUT I want to know if there is a way to this in the way I presented it here. Note: I dont want to use the try catch block bc thats replacing a if statement with another two blocks.
My checkError function:
const checkError = function (error, msg = "Something went wrong") {
if (error) console.error(`${msg}: error`);
//Break original block somehow ¯\_(ツ)_/¯
};
If I were to compress my question it would be: how to break a function with another function. Is there any way to achieve this?
I don't think this is possible. But you could achieve something similar with this:
function checkError (error, msg = "Something went wrong") {
if (!error) return false;
console.error(`${msg}: error`);
return true;
};
db.collection("data").insertOne(
{
key: 'some-data'
}, (error, result) => {
//Return error if something goes wrong - else error is empty
if (checkError(error, "Unable to load database")) return;
console.log("Succes item added")
}
);
Things become easier when you use promises.
Often asynchronous APIs provide a promise interface, and this is also the case for mongodb/mongoose, where you can chain a .exec() call to execute the database query and get a promise in return. This gives you access to the power of JavaScript's async/await syntax. So you can then do like this:
async function main() {
// Connect to database
// ...
// Other db transactions
// ...
let result = await db.collection("data").insertOne({ key: 'some-data'}).exec();
console.log("Item added successfully");
// Any other database actions can follow here using the same pattern
// ...
}
main().catch(err => {
console.log(err);
});
The idea here is that await will throw an exception if the promise returned by .exec() eventually rejects. You can either put a standard try...catch construct around it to deal with that error, or you can just let it happen. In the latter case the promise returned by the wrapping async function will reject. So you can deal with the error at a higher level (like done above).
This way of working also removes the need for numerous nested callbacks. Often you can keep the nesting to just one of two levels by using promises.

npm's guidelines on callback errors

I was reading through npm’s coding style guidelines and came across the following very cryptic suggestion:
Be very careful never to ever ever throw anything. It’s worse than useless. Just send the error message back as the first argument to the callback.
What exactly do they mean and how does one implement this behavior? Do they suggest calling the callback function within itself?
Here’s what I could think of using the async fs.readdir method.
fs.readdir('./', function callback(err, files) {
if (err) {
// throw err // npm says DO NOT do this!
callback(err) // Wouldn’t this cause an infinite loop?
}
else {
// normal stuff
}
})
What they're trying to say is that you should design your modules so the asynchronous functions don't throw errors to catch, but are rather handled inside of a callback (like in the fs.readdir example you provided)...
So, for instance this is what they're saying you should design your module like:
var example = {
logString: function(data, callback){
var err = null;
if (typeof data === "string") {
console.log(data);
} else {
err = {"message": "Data is not a string!"};
}
callback(err);
}
}
They want you to design it so the end user can handle the error inside of the callback instead of using a try/catch statement... For instance, when we use the example object:
example.logString(123, function(err){
// Error is handled in callback instead of try/catch
if (err) console.log(err)
});
This would log {"message": "Data is not a string!"}, because the data doesn't have a typeof equal to "string".
Here is an example of what they're saying you should avoid:
They don't want you to throw errors when you have your asynchronous callback at your disposal... So lets say we redesigned our module so the logString method throws an error instead of passing it into a callback... Like this:
var example = {
logString: function(data, callback){
if (typeof data === "string") {
console.log(data);
} else {
// See, we're throwing it instead...
throw {"message": "Data is not a string!"};
}
callback();
}
}
With this, we have to do the whole try/catch statement, or else you'll get an uncaught error:
try {
example.logString(321, function(){
console.log("Done!")
});
} catch (e) {
console.log(e)
}
Final thoughts / Summary:
The reason I think NPM suggests this method is because it's simply more manageable inside of a asynchronous method.
NodeJS and JavaScript in general likes to have a asynchronous environment so nice to have it all compact into one place, error handling and all.
With the try/catch, it's just one more extra step you have to take, when it could EASILY be handled inside of the callback instead (if you're designing it asynchronously, which you should).
Yes, that would cause an infinite loop. However, they're not talking about that type of callback. Instead, npm is referencing the callbacks used to interact with your module.
To expand upon your example:
module.exports = {
getDirectoryFiles: function (directory, done) {
fs.readdir(directory, function callback(err, files) {
if (err) {
return done(err);
} else {
return done(null, files);
}
})
}
}
You should pass err to the callback from the scope above, not to the function with which you're currently dealing (in the above case, callback). The only reason to name those functions is to help with debugging.
The reason they say not to throw err is because node uses error-first callbacks. Everyone expects your library, if it uses callbacks, to propagate its errors as the first parameter to the callback. For example:
var yourLibrary = require("yourLibrary");
yourLibrary.getDirectoryFiles("./", function (err, files) {
if (err) {
console.log(err);
// do something
} else {
// continue
}
}

Mongoose query doesn't return an error

I am messing aroung with mongoose and I am simply trying to return an object that is in my collection (which to my understanding is the equivalent of a row that is in an SQL table).
In the video I am watching it says that all callbacks from mongoose return error and results but I am only able to return the results never an error (logs as undefined) even when I force an error nothing happens.
How do I find out when I have an error?
here is the code I have.
app = require("../server")
dashboard = require("../models/dashboard")
app.get('/request', (req, res)->
dashboard.users.find({}).exec().then (users, err) ->
console.log err
res.send users
)
The 2nd parameter always comes out undefined no matter what I name it. Also this is written in coffeescript I'll have a javascript version below
var app, dashboard;
app = require("../server");
dashboard = require("../models/dashboard");
app.get('/request', function(req, res) {
return dashboard.users.find({}).exec().then(function(users, err) {
console.log(err);
return res.send(users);
});
});
The first argument being passed to the callback is the error:
dashboard.users.find({}).exec().then (err, users) ->
etc...
Never mind my first answer, I only just noticed that you're using promises, which work different from "regular" callbacks.
With regular callbacks, the first argument passed is always the error value (or null when no error occurred), and the following arguments are result arguments (usually just one, sometimes more than one).
Promises work differently, with two functions:
.then(
function(results) { ... },
function(err) { ... }
)
The first function will be called when the operation (in this case, the find) was succesful. The second function will be called when an error occurred.

Why cant I pull the callback out of the app.get function in node and express.js?

I'm trying to do this in my server.js file:
// doesn't work... don't know why.
var findBooks = function (err, books) {
if (!err) {
return response.send(books);
} else {
console.log(err);
}
}
// Get all books
app.get('/api/books', function (request, response, err, books) {
return BookModel.find( findBooks(err, books));
});
and it gives me a 404 error when I run a jQuery xhr in the console, and the same error when I request the page in the browser. It works if I put the callback in the usual place, moving the findBooks function into the BookModel.find function where it's called and deleting the last two arguments of app.get. I'm working though Chapter 5 of Addy Osmani's Backbone.js Applications book to give you some context. Why doesn't this work? Is doing this a good thing, or am I trying something that isn't recommended?
You have two problems:
findBooks tries to access response, but response is not in scope at that point.
You're calling findBooks with err and books and passing the return value to BookModel.find, when you probably want to pass the function for find to call.
Let's first solve the second problem. Rather than calling the function, just pass it:
return BookModel.find(findBooks);
That was easy. Now, to solve the first problem, we'll need to add an extra argument to findBooks. I'll assume you put it at the start for reasons I'll detail later. Then we can change the usage to look like this:
return BookModel.find(function (err, books) {
return findBooks(response, err, books);
});
It turns out, though, that there's a more concise way to do that, using bind. bind is best known for its use in binding the value of this, but it can also be used to bind arguments:
return BookModel.find(findBooks.bind(null, response));
All together, we get:
function findBooks (response, err, books) {
if (!err) {
return response.send(books);
} else {
console.log(err);
}
}
// Get all books
app.get('/api/books', function (request, response) {
return BookModel.find( findBooks.bind(null, response) );
});

Categories

Resources