exception处理,在promise中引发错误
我正在运行外部代码作为第三方扩展到node.js服务。 API方法返回promise。 一个已经解决的承诺意味着行动成功,一个失败的承诺意味着进行这个行动有一些问题。
现在,这是我有麻烦的地方。
由于第三方代码是未知的,可能会有错误,语法错误,types问题,以及可能导致node.js抛出exception的任何事情。
但是,由于所有代码都包含在承诺中,所以这些抛出的exception实际上是作为失败的承诺而回来的。
我试图把函数调用放在一个try / catch块中,但是它从来没有被触发:
// worker process var mod = require('./3rdparty/module.js'); try { mod.run().then(function (data) { sendToClient(true, data); }, function (err) { sendToClient(false, err); }); } catch (e) { // unrecoverable error inside of module // ... send signal to restart this worker process ... });
在上面的伪代码示例中,当抛出一个错误时,它在失败的promise函数中出现,而不在catch中。
从我所读到的,这是一个function,而不是一个问题,承诺。 然而,我总是无法理解,为什么你总是想要把exception和预期的拒绝完全一样。
一种情况是关于代码中的实际错误,可能无法恢复 – 另一种情况是可能缺lessconfiguration信息,参数或可恢复的东西。
谢谢你的帮助!
崩溃并重新启动进程不是一个有效的策略来处理错误,甚至不是错误。 在Erlang这个过程很便宜,并且做一个孤立的事情,比如为一个客户提供服务,这样可以。 这不适用于节点,其中一个过程的成本要高出数倍,并且一次为数千个客户提供服务
假设您的服务每秒有200个请求。 如果有1%的人在你的代码中投掷了一条path,那么你每秒钟会得到20次进程closures,大约每50msclosures一次。 如果你有4个内核,每个内核有1个进程,你会在200毫秒内丢失它们。 因此,如果一个进程需要200ms以上的时间才能启动并准备好为请求提供服务(对于不加载任何模块的节点进程,最低成本约为50ms),现在我们已经成功地拒绝了全部服务。 更不用说,用户遇到错误往往会做一些事情,比如反复刷新页面,从而使问题复杂化。
域名不能解决问题,因为他们不能确保资源不泄漏 。
阅读更多问题#5114和#5149
但是,promise会捕获所有exception,然后以非常类似于同步exception如何传播的方式传播它们。 另外,他们经常会提供一种方法, finally
意味着等同于try...finally
由于这两个特性,我们可以构build“上下文pipe理器”(如在Python中 ),它总是清理资源:
function using(resource, fn) { // wraps it in case the resource was not promise var pResource = Promise.cast(resource); return pResource.then(fn).finally(function() { return pResource.then(function(resource) { return resource.dispose(); }); }); }
然后像这样使用它们:
function connectAndFetchSomething(...) { return using(client.connect(host), function(conn) { var stuff = JSON.parse(something); return conn.doThings(stuff).then(function(res) { return conn.doOherThingWith(JSON.parse(res)); )); }); });
在使用fn
参数返回的promise链完成后,资源将始终被处置。 即使在该函数内部(例如从JSON.parse
)或其内部的.then
闭包(如第二个JSON.parse
)中引发错误,或者如果链中的承诺被拒绝(等同于调用带有错误的callback),也是如此。 这就是为什么它承诺错误和传播它的重要性。
编辑:但是,我们如何处理跟随抛掷范例的库呢? 我们不能确保他们已经清理了资源 – 我们如何避免承诺颠覆他们的例外?
通常这些库使用节点样式的callback,我们需要用承诺来包装它们。 例如,我们可能有:
function unwrapped(arg1, arg2, done) { var resource = allocateResource(); mayThrowError1(); resource.doesntThrow(arg1, function(err, res) { mayThrowError2(arg2); done(err, res); }); }
mayThrowError2()
在内部callback中,即使在另一个promise中调用mayThrowError2()
,它仍然会崩溃进程。
然而, mayThrowError1()
会在被调用的时候被promise所捕获,然后内部分配的资源就会泄漏。
我们可以用一种方法来包装这个函数,以确保抛出的错误是不可恢复的,并使进程崩溃:
function wrapped(arg1, arg2) { var defer = Promise.pending(); try { unwrapped(arg1, arg2, function callback(err, res) { if (err) defer.reject(err); else defer.fulfill(res); }); } catch (e) { process.nextTick(function rethrow() { throw e; }); } }
在另一个promise的.then
callback中使用包装的函数.then
现在如果解包抛出,则会导致进程崩溃,回落到throw-crash范例。
它总的希望,当你使用越来越多的基于承诺的库时,他们会使用上下文pipe理器模式来pipe理他们的资源,因此你不需要让进程崩溃。
这些解决scheme都不是防弹的 – 甚至不会引发错误。 它很容易意外地写代码泄漏资源,尽pipe不扔。 例如,这个节点样式函数会泄漏资源,即使它不抛出:
function unwrapped(arg1, arg2, done) { var resource = allocateResource(); resource.doSomething(arg1, function(err, res) { if (err) return done(err); resource.doSomethingElse(res, function(err, res) { resource.dispose(); done(err, res); }); }); }
为什么? 因为当doSomething
的callback接收到错误时,代码会忘记处理资源。
上下文pipe理器不会发生这种问题。 你不能忘记打电话处置:你不必,因为using
它为您服务!
参考文献: 为什么我要切换到承诺 , 上下文pipe理器和事务
这几乎是承诺的最重要的特点。 如果它不在那里,你可以使用callback:
var fs = require("fs"); fs.readFile("myfile.json", function(err, contents) { if( err ) { console.error("Cannot read file"); } else { try { var result = JSON.parse(contents); console.log(result); } catch(e) { console.error("Invalid json"); } } });
(在你说JSON.parse
是js中唯一的东西之前,你知道把variables强制转换成一个数字如+a
会抛出TypeError
吗?
但是,上面的代码可以用promise来更清楚地expression,因为只有一个exception通道而不是2:
var Promise = require("bluebird"); var readFile = Promise.promisify(require("fs").readFile); readFile("myfile.json").then(JSON.parse).then(function(result){ console.log(result); }).catch(SyntaxError, function(e){ console.error("Invalid json"); }).catch(function(e){ console.error("Cannot read file"); });
注意catch
是糖的.then(null, fn)
。 如果你了解exceptionstream程是如何工作的,你会发现它通常使用.then(fnSuccess, fnFail)
。
关键不是要做什么。然后.then(success, fail)
结束, function(fail, success)
(IE它不是一个可选的方式来附加您的callback),但使书面代码看起来几乎相同,同步代码:
try { var result = JSON.parse(readFileSync("myjson.json")); console.log(result); } catch(SyntaxError e) { console.error("Invalid json"); } catch(Error e) { console.error("Cannot read file"); }
(实际上同步代码实际上是丑陋的,因为javascript没有input捕捉)
承诺拒绝只是一个失败的抽象。 那么节点式的callback(err,res)和exception也是如此。 由于承诺是asynchronous的,所以不能使用try-catch来实际捕获任何东西,因为错误可能不会发生在事件循环的相同记号中。
一个简单的例子:
function test(callback){ throw 'error'; callback(null); } try { test(function () {}); } catch (e) { console.log('Caught: ' + e); }
在这里,我们可以捕获一个错误,因为函数是同步的(虽然基于callback)。 另一个:
function test(callback){ process.nextTick(function () { throw 'error'; callback(null); }); } try { test(function () {}); } catch (e) { console.log('Caught: ' + e); }
现在我们不能抓住这个错误! 唯一的select是在callback中传递它:
function test(callback){ process.nextTick(function () { callback('error', null); }); } test(function (err, res) { if (err) return console.log('Caught: ' + err); });
现在,它的工作方式与第一个示例中的相同。承诺也是如此:您不能使用try-catch,因此您使用拒绝来处理错误。