如果从异步回调中抛出错误,会发生什么事?让我们先来做个测试。 EventModel/nestedErrors.js setTimeout(function A() { setTimeout(function B() { setTimeout(function C() { throw new Error('Something terrible has happened!'); }, 0); }, 0); }, 0); 上述应用的结果是一条极其简短的堆栈轨迹。 Error: Something terrible has happened! at Timer.C (/AsyncJS/nestedErrors.js:4:13) 等等,A和B发生了什么事?为什么它们没有出现在堆栈轨迹中?这是因为运行C的时候,A和B并不在内存堆栈里。这3个函数都是从事件队列直接运行的。 基于同样的理由,利用try/catch语句块并不能捕获从异步回调中抛出的错误。下面进行演示。 EventModel/asyncTry.js try { setTimeout(function() { throw new Error('Catch me if you can!'); }, 0); } catch (e) { console.error(e); } 看到这里的问题了吗?这里的try/catch语句块只捕获setTimeout函数自身内部发生的那些错误。因为setTimeout异步地运行其回调,所以即使延时设置为0,回调抛出的错误也会直接流向应用程序的未捕获异常处理器(请参阅1.4.2节)。 总的来说,取用异步回调的函数即使包装上try/catch语句块,也只是无用之举。(特例是,该异步函数确实是在同步地做某些事且容易出错。例如,Node的fs.watch(file,callback)就是这样一个函数,它在目标文件不存在时会抛出一个错误。)正因为此,Node.js中的回调几乎总是接受一个错误作为其首个参数,这样就允许回调自己来决定如何处理这个错误。举个例子,下面这个Node应用尝试异步地读取一个文件,还负责记录下任何错误(如“文件不存在”)。 EventModel/readFile.js var fs = require('fs'); fs.readFile('fhgwgdz.txt', function(err, data) { if (err) { return console.error(err); }; console.log(data.toString('utf8')); }); 客户端JavaScript库的一致性要稍微差些,不过最常见的模式是,针对成败这两种情形各规定一个单独的回调。jQuery的Ajax方法就遵循了这个模式。 $.get('/data', { success: successHandler, failure: failureHandler }); 不管API形态像什么,始终要记住的是,只能在回调内部处理源于回调的异步错误。异步尤达大师会说:“做,或者不做,没有试试看一说。”