为什么多层异步函数不捕获节点中最低级别抛出的错误?
Why doesn't multilayered async functions catch error thrown at lowest level in node?
我正在尝试测试某些邮件代码的故障模式,这些代码在最低级别可能会引发错误。测试和抛出函数之间的所有层都是异步的,并在它们下面的函数上使用 await 。在顶层(也在异步函数中,我有一个 try catch 块。但是节点在错误传播到此级别之前抛出未处理的 promise 异常。
我的测试代码是这样的
beforeEach(function() {
//set default values - tests can change them
this.reasons = '';
this.reschedules = 0;
this.params.cid = 35124;
this.startTest = async () => {
/* this.confirmation is an async function under test,
this.mailer is a mock mailer with an async "send" method
which will throw an error in the correct test */
const doner = this.confirmation(this.mailer);
// ..other actions related to mocking database access made by confirmation
await doner;
return this.mailer.maildata; //provide info on parameters passed to this.mailer
};
});
it('Failure to send is reported', async function() {
this.mailer.sendResolve = false; //tell mock mailer to fail send request
try {
await this.startTest();
expect(true).to.be.false;
} catch(err) {
expect(err).to.be.instanceOf(Error);
}
});
mock mailer有点像这样
class Mailer {
constructor(user,params){
...
}
...
async send(subject, to, cc, bcc) {
this.maildata.subject = subject;
if (to !== undefined) this.maildata.to = to;
if (cc !== undefined) this.maildata.cc = cc;
if (bcc !== undefined) this.maildata.bcc = bcc;
if (!this.sendResolve) throw new Error('Test Error');
}
...
}
和被测代码摘要
module.exports = async function(mailer) {
//get confirm data from database
const cData = await confirm(mailer.params.cid, mailer.db);
if (cData.count > 0) {
// ... format the email message and build it into maildata
await mailer.send(
subject,
emailAddress,
null,
process.env.PAS_MAIL_FROM,
{
pid:cData.pid,
type: 'confirmation',
extra: `Calendar ID ${mailer.params.cid} with procedure ${cData.procedure}`
}
);
debug('message sent, update the database');
await mailer.db.exec(async connection => {
...
});
debug('success');
} else {
debug('invalid calendarid');
throw new Error('Invalid Calendar ID');
}
};
可以看出,从async send
函数返回堆栈到try {}catch(){}
的调用路径都是异步函数。但是当我 运行 这个测试节点输出一个未处理的承诺拒绝时。
我已经尝试使用 visual studio 代码调试器来单步执行此操作,我有点迷失在包装异步函数以将它们变成承诺提供者的机制中。据我所知,一层错误处理正确,然后在下一层失败。
这是否意味着每个异步函数都必须有一个 try catch 块来捕获并重新抛出任何错误?我找不到任何说明我必须这样做的解释。
回答你的问题:
Does this mean that every async function must have a try catch block to catch and rethrow any error?
如您所料,错误会通过 await
-ed 调用向上传播:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
await inner();
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === 'something bad happened'); // Success!
});
...所以不,你不需要在每个级别都有一个 try / catch
块。
追踪未处理的 Promise
拒绝
我无法确切地看到 await
链在您的示例代码中可能被破坏的位置,但为了帮助追踪未处理的 Promise
拒绝,您可以添加 process handler for the unhandledRejection
event并查看记录的 Promise
以查看拒绝从何处开始并从那里向后跟踪调用堆栈:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
inner(); // <= this will cause an Unhandled Rejection
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === undefined); // Success! (broken await chain)
})
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p);
console.log('reason:', reason);
});
...在这种情况下记录:
Unhandled Rejection at: Promise {
<rejected> Error: something bad happened
at inner (.../code.test.js:12:9)
at inner (.../code.test.js:8:3)
at middle (.../code.test.js:4:9) // <= this is the broken link
at Context.outer (.../code.test.js:18:11)
at callFn (...\node_modules\mocha\lib\runnable.js:387:21)
...
...这将我们指向 inner
中抛出的 Error
,通过追踪链,我们发现 middle
是损坏的 link。
我正在尝试测试某些邮件代码的故障模式,这些代码在最低级别可能会引发错误。测试和抛出函数之间的所有层都是异步的,并在它们下面的函数上使用 await 。在顶层(也在异步函数中,我有一个 try catch 块。但是节点在错误传播到此级别之前抛出未处理的 promise 异常。
我的测试代码是这样的
beforeEach(function() {
//set default values - tests can change them
this.reasons = '';
this.reschedules = 0;
this.params.cid = 35124;
this.startTest = async () => {
/* this.confirmation is an async function under test,
this.mailer is a mock mailer with an async "send" method
which will throw an error in the correct test */
const doner = this.confirmation(this.mailer);
// ..other actions related to mocking database access made by confirmation
await doner;
return this.mailer.maildata; //provide info on parameters passed to this.mailer
};
});
it('Failure to send is reported', async function() {
this.mailer.sendResolve = false; //tell mock mailer to fail send request
try {
await this.startTest();
expect(true).to.be.false;
} catch(err) {
expect(err).to.be.instanceOf(Error);
}
});
mock mailer有点像这样
class Mailer {
constructor(user,params){
...
}
...
async send(subject, to, cc, bcc) {
this.maildata.subject = subject;
if (to !== undefined) this.maildata.to = to;
if (cc !== undefined) this.maildata.cc = cc;
if (bcc !== undefined) this.maildata.bcc = bcc;
if (!this.sendResolve) throw new Error('Test Error');
}
...
}
和被测代码摘要
module.exports = async function(mailer) {
//get confirm data from database
const cData = await confirm(mailer.params.cid, mailer.db);
if (cData.count > 0) {
// ... format the email message and build it into maildata
await mailer.send(
subject,
emailAddress,
null,
process.env.PAS_MAIL_FROM,
{
pid:cData.pid,
type: 'confirmation',
extra: `Calendar ID ${mailer.params.cid} with procedure ${cData.procedure}`
}
);
debug('message sent, update the database');
await mailer.db.exec(async connection => {
...
});
debug('success');
} else {
debug('invalid calendarid');
throw new Error('Invalid Calendar ID');
}
};
可以看出,从async send
函数返回堆栈到try {}catch(){}
的调用路径都是异步函数。但是当我 运行 这个测试节点输出一个未处理的承诺拒绝时。
我已经尝试使用 visual studio 代码调试器来单步执行此操作,我有点迷失在包装异步函数以将它们变成承诺提供者的机制中。据我所知,一层错误处理正确,然后在下一层失败。
这是否意味着每个异步函数都必须有一个 try catch 块来捕获并重新抛出任何错误?我找不到任何说明我必须这样做的解释。
回答你的问题:
Does this mean that every async function must have a try catch block to catch and rethrow any error?
如您所料,错误会通过 await
-ed 调用向上传播:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
await inner();
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === 'something bad happened'); // Success!
});
...所以不,你不需要在每个级别都有一个 try / catch
块。
追踪未处理的 Promise
拒绝
我无法确切地看到 await
链在您的示例代码中可能被破坏的位置,但为了帮助追踪未处理的 Promise
拒绝,您可以添加 process handler for the unhandledRejection
event并查看记录的 Promise
以查看拒绝从何处开始并从那里向后跟踪调用堆栈:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
inner(); // <= this will cause an Unhandled Rejection
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === undefined); // Success! (broken await chain)
})
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p);
console.log('reason:', reason);
});
...在这种情况下记录:
Unhandled Rejection at: Promise {
<rejected> Error: something bad happened
at inner (.../code.test.js:12:9)
at inner (.../code.test.js:8:3)
at middle (.../code.test.js:4:9) // <= this is the broken link
at Context.outer (.../code.test.js:18:11)
at callFn (...\node_modules\mocha\lib\runnable.js:387:21)
...
...这将我们指向 inner
中抛出的 Error
,通过追踪链,我们发现 middle
是损坏的 link。