asynchronousnode.js调用中的error handling

我是新来的node.js,虽然我一般熟悉JavaScript。 我的问题是关于如何处理node.js中的错误的“最佳实践”。

通常,在编写Web服务器,FastCGI服务器或各种语言的网页时,我在multithreading环境中使用阻塞处理程序的exception。 当一个请求进来,我通常做这样的事情:

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"); } 

这样,我总是可以处理内部错误,并发送有效的响应给用户。

我明白,与node.js应该做非阻塞调用,这显然会导致不同数量的callback,如在这个例子中:

 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 } 

这个例子将完全终止服务器,因为exception没有被捕获。 我的问题是在这里,我不能再使用一个单一的try / catch,因此通常不会在处理请求时发现任何可能出现的错误。

当然,我可以在每个callback中添加一个try / catch,但是我不喜欢这种方法,因为那么程序员就不会忘记 try / catch。 对于具有许多不同且复杂的处理程序的复杂服务器,这是不可接受的。

我可以使用全局exception处理程序(防止完整的服务器崩溃),但是我不能发送响应给用户,因为我不知道哪个请求导致exception。 这也意味着请求仍然未处理/打开,浏览器正在等待响应。

有人有一个好的,坚如磐石的解决scheme?

节点0.8引入了一个名为“域”的新概念。 它们与.net中的AppDomain非常相似,并提供了封装一组IO操作的方法。 他们基本上允许你把你的请求处理调用包装在上下文特定的组中。 如果这个组抛出任何未被捕获的exception,那么它们可以被处理和处理,使得你可以访问所有需要的范围和上下文特定的信息,以便成功从错误中恢复(如果可能的话)。

这个function是新的,刚刚被引入,所以谨慎使用,但是从我可以告诉它已经被专门引入来处理OP正在试图解决的问题。

文档可以在http://nodejs.org/api/domain.htmlfind

检出node.js中的uncaughtException处理程序 它捕获了引发事件循环的抛出错误。

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

但不抛出错误总是一个更好的解决scheme。 你可以做一个return res.end('Unabled to load file xxx');

这是Node现在的问题之一。 追踪哪个请求导致callback中出现错误几乎是不可能的。

如果可能的话,你将不得不在callback中自己处理你的错误(如果你仍然有对请求和响应对象的引用)。 uncaughtException处理程序将阻止节点进程退出,但引起exception的请求首先从用户的angular度挂起。

很好的问题。 我现在正在处理同样的问题。 可能最好的方法是使用uncaughtException 。 请求对象和请求对象的引用不是问题,因为可以将它们包装到exception对象中,即传递给uncaughtException事件。 像这样的东西:

 var HttpException = function (request, response, message, code) { this.request = request; this.response = response; this.message = message; this.code = code || 500; } 

丢它:

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

并处理响应:

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

我还没有testing这个解决scheme,但我不明白为什么这不起作用的原因。

我给我自己的问题的答案… 🙂

因为似乎没有办法手动捕捉错误。 我现在使用一个辅助函数,它本身返回一个包含try / catch块的函数 。 此外,我自己的Web服务器类检查请求处理函数是调用response.end() 还是 try / catch帮助函数waitfor() (否则引发exception)。 这在很大程度上避免了这个请求被开发者错误地忽略了。 这不是一个100%容易出错的解决scheme,但对我来说已经足够好了。

 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); } } } 

这样我只需要添加一个内联的waitfor()调用来保证安全。

 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 } 

实际的检查机制稍微复杂一点,但应该清楚它是如何工作的。 如果有人有兴趣,我可以在这里发布完整的代码。

一个想法:你可以使用助手的方法来创build你的callback,并使之成为你的标准做法。 这确实给开发者带来了沉重的负担,但是至less你可以有一个“标准”的方式来处理你的callback,以至于忘记一个callback的机会很低:

 var callWithHttpCatch = function(response, fn) { try { fn && fn(); } catch { response.writeHead(500, {'Content-Type': 'text/plain'}); //No } } <snipped> var buffer = new require('buffer').Buffer(10); fs.read(fd, buffer, 0, 10, null, function(error, bytesRead, buffer) { callWithHttpCatch(response, buffer.dontTryThisAtHome()); // causes exception response.end(buffer); }); //fs.read }); //fs.open 

我知道这可能不是你正在寻找的答案,但是关于ECMAScript(或者一般的函数式编程)的好处之一就是你可以很容易地为自己的工具推出类似的东西。

在写这篇文章时,我看到的方法是使用“承诺”。

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

这些允许代码和callback针对错误pipe理进行良好的结构化,并使其更具可读性。 它主要使用.then()函数。

 someFunction().then(success_callback_func, failed_callback_func); 

这是一个基本的例子:

  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."); 

有两件事真的帮助我解决了我的代码中的这个问题。

  1. 'longjohn'模块,可以让你看到完整的堆栈跟踪(跨越多个asynchronouscallback)。
  2. 一个简单的闭包技术,用于在标准callback(err, data)习惯用法(在这里显示在CoffeeScript中)中保留exception。

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

现在,您可以包装不安全的代码,并且您的callback函数都以相同的方式处理错误:通过检查错误参数。

我最近创build了一个名为WaitFor的简单抽象,以同步模式调用asynchronous函数(基于Fibers): https : //github.com/luciotato/waitfor

“坚如磐石”太新了。

使用wait.for可以使用async函数,就好像它们是同步的,而不会阻塞节点的事件循环。 这几乎是你习惯于:

 var wait=require('wait.for'); function handleRequest(request, response) { //launch fiber, keep node spinning wait.launchFiber(handleinFiber,request, response); } function handleInFiber(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, callback) { if (something) throw new Error("something bad happened"); Response.end("OK"); } 

由于您处于光纤中,因此可以按顺序编程“阻塞光纤”,而不是节点的事件循环。

另一个例子:

 var sys = require('sys'), fs = require('fs'), wait = require('wait.for'); require("http").createServer( function(req,res){ wait.launchFiber(handleRequest,req,res) //handle in a fiber ).listen(8124); function handleRequest(request, response) { try { var fd=wait.for(fs.open,"/proc/cpuinfo", "r"); console.log("File open."); var buffer = new require('buffer').Buffer(10); var bytesRead=wait.for(fs.read,fd, buffer, 0, 10, null); buffer.dontTryThisAtHome(); // causes exception response.end(buffer); } catch(err) { response.end('ERROR: '+err.message); } } 

正如你所看到的,我使用wait.for在同步模式下调用节点的asynchronous函数,没有(可见的)callback,所以我可以在一个try-catch块中包含所有的代码。

如果任何一个asynchronous函数返回err!== null, wait.for将会抛出一个exception

更多信息在https://github.com/luciotato/waitfor

同样,在同步multithreading编程(例如.NET,Java,PHP)中,当捕获到自定义的未知exception时,您不能将任何有意义的信息返回给客户端。 如果您没有关于例外的信息,您可能会返回HTTP 500。

因此,“秘密”在于填充一个描述性的错误对象,这样你的error handling器可以从有意义的错误映射到正确的HTTP状态+可选的描述性结果。 但是,在到达process.on('uncaughtException')之前,您还必须捕获该exception:

第一步:定义一个有意义的错误对象

 function appError(errorCode, description, isOperational) { Error.call(this); Error.captureStackTrace(this); this.errorCode = errorCode; //...other properties assigned here }; appError.prototype.__proto__ = Error.prototype; module.exports.appError = appError; 

第二步:抛出一个exception时,填充属性(见第一步),允许处理程序将其转换为meannigul HTTP结果:

 throw new appError(errorManagement.commonErrors.resourceNotFound, "further explanation", true) 

第3步:调用一些潜在的危险代码时,捕获错误并重新抛出该错误,同时在Error对象中填充其他上下文属性

第四步:您必须在请求处理过程中捕获exception。 如果你使用一些领先的Promise库(BlueBird很棒),这可以让你捕捉asynchronous错误。 如果你不能使用promise,那么任何内build的NODE库都会在callback中返回错误。

第5步:现在您的错误被捕获并包含有关发生的描述性信息,您只需将其映射到有意义的HTTP响应。 这里最好的部分是你可能有一个集中的,单一的error handling程序来获取所有的错误,并将它们映射到HTTP响应:

  //this specific example is using Express framework res.status(getErrorHTTPCode(error)) function getErrorHTTPCode(error) { if(error.errorCode == commonErrors.InvalidInput) return 400; else if... } 

您可以在这里find其他相关的最佳做