error handling in asynchronous node.js calls

2019-01-21 05:37发布

I'm new to node.js although I'm pretty familiar with JavaScript in general. My question is regarding "best practices" on how to handle errors in node.js.

Normally when programming web servers, FastCGI servers or web pages in various languages I'm using Exceptions with blocking handlers in a multi-threading environment. When a request comes in I usually do something like this:

function handleRequest(request, response) {
  try {

    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

This way I can always handle internal errors and send a valid response to the user.

I understand that with node.js one is supposed to do non-blocking calls which obviously leads to various number of callbacks, like in this example:

var sys    = require('sys'),
    fs     = require('fs');

require("http").createServer(handleRequest).listen(8124);

function handleRequest(request, response) {

  fs.open("/proc/cpuinfo", "r",
    function(error, fd) {
      if (error)
        throw new Error("fs.open error: "+error.message);

      console.log("File open.");

      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          buffer.dontTryThisAtHome();  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

}

This example will kill the server completely because exceptions aren't being catched. My problem is here that I can't use a single try/catch anymore and thus can't generally catch any error that may be raised during the handling of the request.

Of course I could add a try/catch in each callback but I don't like that approach because then it's up to the programmer that he doesn't forget a try/catch. For a complex server with lots of different and complex handlers this isn't acceptable.

I could use a global exception handler (preventing the complete server crash) but then I can't send a response to the user since I don't know which request lead to the exception. This also means that the request remains unhandled/open and the browser is waiting forever for a response.

Does someone have a good, rock solid solution?

10条回答
成全新的幸福
2楼-- · 2019-01-21 05:59

I give an answer to my own question... :)

As it seems there is no way around to manually catch errors. I now use a helper function that itself returns a function containing a try/catch block. Additionally, my own web server class checks if either the request handling function calls response.end() or the try/catch helper function waitfor() (raising an exception otherwise). This avoids to a great extent that request are mistakenly left unprotected by the developer. It isn't a 100% error-prone solution but works well enough for me.

handler.waitfor = function(callback) {
  var me=this;

  // avoid exception because response.end() won't be called immediately:
  this.waiting=true;

  return function() {
    me.waiting=false;
    try {
      callback.apply(this, arguments);

      if (!me.waiting && !me.finished)
        throw new Error("Response handler returned and did neither send a "+
          "response nor did it call waitfor()");

    } catch (e) {
      me.handleException(e);
    }
  }
}

This way I just have to add a inline waitfor() call to be on the safe side.

function handleRequest(request, response, handler) {
  fs.read(fd, buffer, 0, 10, null, handler.waitfor(
    function(error, bytesRead, buffer) {

      buffer.unknownFunction();  // causes exception
      response.end(buffer);
    }
  )); //fs.read
}

The actual checking mechanism is a little more complex, but it should be clear how it works. If someone is interested I can post the full code here.

查看更多
The star\"
3楼-- · 2019-01-21 06:01

Very good question. I'm dealing with the same problem now. Probably the best way, would be to use uncaughtException. The reference to respone and request objects is not the problem, because you can wrap them into your exception object, that is passed to uncaughtException event. Something like this:

var HttpException = function (request, response, message, code) {

  this.request = request;
  this.response = response;  
  this.message = message;    
  this.code = code || 500;

}

Throw it:

throw new HttpException(request, response, 'File not found', 404);

And handle the response:

process.on('uncaughtException', function (exception) {
  exception.response.writeHead(exception.code, {'Content-Type': 'text/html'});
  exception.response.end('Error ' + exception.code + ' - ' + exception.message);
});

I haven't test this solution yet, but I don't see the reason why this couldn't work.

查看更多
姐就是有狂的资本
4楼-- · 2019-01-21 06:04

At the time of this writing, the approach I am seeing is to use "Promises".

http://howtonode.org/promises
https://www.promisejs.org/

These allow code and callbacks to be structured well for error management and also makes it more readable. It primarily uses the .then() function.

someFunction().then(success_callback_func, failed_callback_func);

Here's a basic example:

  var SomeModule = require('someModule');

  var success = function (ret) {
      console.log('>>>>>>>> Success!');
  }

  var failed = function (err) {
    if (err instanceof SomeModule.errorName) {
      // Note: I've often seen the error definitions in SomeModule.errors.ErrorName
      console.log("FOUND SPECIFIC ERROR");
    }
    console.log('>>>>>>>> FAILED!');
  }

  someFunction().then(success, failed);
  console.log("This line with appear instantly, since the last function was asynchronous.");
查看更多
在下西门庆
5楼-- · 2019-01-21 06:08

Checkout the uncaughtException handler in node.js. It captures the thrown errors that bubble up to the event loop.

http://nodejs.org/docs/v0.4.7/api/process.html#event_uncaughtException_

But not throwing errors is always a better solution. You could just do a return res.end('Unabled to load file xxx');

查看更多
够拽才男人
6楼-- · 2019-01-21 06:08

Two things have really helped me solve this problem in my code.

  1. The 'longjohn' module, which lets you see the full stack trace (across multiple asyncronous callbacks).
  2. A simple closure technique to keep exceptions within the standard callback(err, data) idiom (shown here in CoffeeScript).

    ferry_errors = (callback, f) ->
      return (a...) ->
        try f(a...)
        catch err
          callback(err)
    

Now you can wrap unsafe code, and your callbacks all handle errors the same way: by checking the error argument.

查看更多
聊天终结者
7楼-- · 2019-01-21 06:13

This is one of the problems with Node right now. It's practically impossible to track down which request caused an error to be thrown inside a callback.

You're going to have to handle your errors within the callbacks themselves (where you still have a reference to the request and response objects), if possible. The uncaughtException handler will stop the node process from exiting, but the request that caused the exception in the first place will just hang there from the user point of view.

查看更多
登录 后发表回答