我试图断言在快速中间件中抛出异步的错误:
要测试的中间件:
const request = require('request');
const middleware = function (options) {
if (!options) {
throw new Error('Options are missing.'); // gets catched
}
request(options.uri, (err, response) => {
if(err) {
throw err;
}
});
return function (req, res, next) {}
}
module.exports = middleware;
mocha
测试:
describe('middleware', () => {
describe('if async error is thrown', () => {
it('should return an error', done => {
try {
middleware({
uri: 'http://unkown'
});
} catch (err) {
assert.equal('Error: getaddrinfo ENOTFOUND unkown unkown:80', err.toString());
return done();
}
});
});
})
问题是,err
没有进入测试范围内:
Uncaught Error: getaddrinfo ENOTFOUND unkown unkown:80
at errnoException (dns.js:27:10)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:78:26)
我知道发生这种情况是因为错误是异步的,但我不知道如何在这里解决它。
答案 0 :(得分:1)
基本问题是您在异步代码中throw
。简而言之:不要这样做,这很糟糕; - )
所以,恕我直言,你有几个选择。哪一个最适合您取决于您的使用案例。
将同步变为异步设置功能,即不要return
中间件功能,而是使用回调将其交给呼叫者。这样调用者的代码变得更糟,但是你有一个干净的同步/异步代码拆分,并且不要混合那些不适合混合的范例。
const middleware = function (options, callback) {
if (!options) {
throw new Error('Options are missing.');
}
request(options.uri, (error, response, body) => {
if (error) {
return callback(error);
}
callback(null, function (req, res, next) {
// ...
next();
});
});
};
不是每次调用request
,而是每次运行中间件时都这样做。我不知道这是否有意义,但这样你总是可以同步返回,只需处理中间件本身的异步。
const middleware = function (options, callback) {
if (!options) {
throw new Error('Options are missing.');
}
return function (req, res, next) {
request(options.uri, (error, response, body) => {
if (error) {
return next(error);
}
// ...
next();
});
});
};
第三个,恕我直言最好的选择是外包请求,并将结果移交给中间件的设置功能,而不是让设置功能执行请求。
这不仅解决了你的同步问题和异步问题,这也使得测试变得更容易,因为你不依赖于HTTP调用,而是可以手动移交所需的结果。
答案 1 :(得分:0)
当您使用异步代码时,try-catch
& throw
无法帮助您,因为他们只处理同步代码。
解决方法
const request = require('request');
const middleware = function(options) { // <--- outer scope
var api_output = null; // Store the output of the HTTP request here
if (!options) throw new Error('Options are missing.'); // Express shouldn't handle this error, hence we throw it (also this error is synchronous)
return function(req, res, next) { // <--- inner scope
if (!api_output) { // Check if the output of the HTTP request has already been saved (so as to avoid calling it repeatedly)
request(options.uri, (error, response, body) => { // Perform the HTTP request
if (error) return next(error); // Pass the error to express error-handler
api_output = { // Save the output of the HTTP request in the outer scope
response: response,
body: body
};
req.api_output = api_output; // Set the output of the HTTP request in the req so that next middleware can access it
next(); // Call the next middleware
});
} else { // If the output of the HTTP request is already saved
req.api_output = api_output; // Set the output of the HTTP request in the req so that next middleware can access it
next(); // Call the next middleware
}
};
}
module.exports = middleware;
我所做的只是返回一个快速中间件,只有在尚未调用的情况下调用外部API 。如果没有错误,那么它会将外部API的响应存储在api_output
中,并将其传递给下一个可以使用它的快速中间件。
要了解其工作原理,了解JavaScript如何处理范围(look up closure
)至关重要。
每次执行快速中间件时,我都没有调用第三方API,而是在第一次调用它时将该API的输出存储在快速中间件函数的外部范围内 。这样做的好处是第三方API的输出对外部和外部私有。内部范围,无法从应用程序中的任何其他位置访问。
如果有任何错误,则将其传递给next
回调(这会触发快速错误处理函数)。请参阅"Express error handling",了解其工作原理。特别是这个 -
如果您将任何内容传递给next()函数(字符串&#39; route&#39;除外),则Express会将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。
您可以在创建路线时以这种方式使用上述功能:
const router = require('express').Router();
router
.get('/my-route', middleware({
uri: 'my-url'
}), function(req, res, next) {
// here you can access req.api_output.response & req.api_output.body
// don't forget to call next once processing is complete
});
现在,关于测试
在您的mocha测试框架中,您可以使用request
模块调用API,就像在中间件函数中使用它来调用外部API一样。然后你可以轻松断言输出:
try-catch
来处理来自中间件功能的Error
注意我使用了术语&#34; express-middleware&#34;仅指代function(req, res, next) {}
等中间件。我已经使用了术语&#34;中间件&#34;引用名为middleware
的函数。
答案 2 :(得分:0)
在middleware
函数中,从try-catch返回一个promise。所以你的catch
块会拒绝(错误)。
我会做这样的事情:
const request = require('request');
const middleware = function (options) {
return new Promise((resolve,reject)=> {
if (!options) {
reject('Options are missing.');
}
request(options.uri, (err, response) => {
if(err) {
reject(err)
} else {
resolve(response)
}
});
});
}
然后,对于您的测试用例,请执行以下操作:
describe('middleware', () => {
describe('if async error is thrown', () => {
it('should return an error', (done) => {
middleware({uri: 'http://unkown'}).then((res)=>{
done(res) //this will fail because you expect to never hit this block
},(err)=>{
assert.equal('Error: getaddrinfo ENOTFOUND unkown unkown:80', err.toString());
done()
});
});
});
})