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的.thencallback中使用包装的函数.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,因此您使用拒绝来处理错误。