async await (try/catch) in NodeJS



我正在研究一个 NodeJS API,我想知道ROUTES 文件中的以下 2 个示例中的哪一个是最佳实践。正如你所看到的,在一个我添加了try/catch,而在另一个我没有。请注意,在服务.js文件中,我也添加了try/catch。我只需要在一个地方使用它还是在两个地方使用它更好?

路由文件

const todo = router => {
/* EXAMPLPE 1 */
router.get('/tasks', isAuth, async (req, res, next) => {
const results = await TodoService.getTasks();
return res.status(201).json(results);
});
/* EXAMPLPE 2 */
router.get('/tasks', isAuth, async (req, res, next) => {
try {
const results = await TodoService.getTasks();
return res.status(201).json(results);
} catch (e) {
return next(e);
}
};

服务

.JS
class TodoService {
static async getTasks() {
try {
const tasks = await TaskModel.find();
return {
message: 'Fetched posts successfully.',
tasks: tasks,
status: 200
};
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
return err;
}
}
}

在你问题的现在编辑版本中,getTasks()永远不能拒绝它的承诺(因为你抓住了任何可能的拒绝并将它们变成一个已解决的承诺)。 因此,示例 2 中的调用方包含一个完全无意义的try/catch,基本上是永远无法使用的死代码。 虽然看起来它可能是更安全的代码,但它基本上是浪费的代码。

在函数的文档/合同中明确是否可以拒绝。 如果不能,那么调用方就没有理由尝试处理拒绝,因为这只是毫无意义的代码。

就我个人而言,我认为让错误通过被拒绝的承诺流回可能更灵活,因为它可以在更多地方使用,并且更容易与其他操作结合使用。 但是,如果唯一的目的是作为请求处理程序的单个部分,那么它可以保存代码以集中拒绝捕获并将其转换为您像现在一样返回的已解决对象。 对于不同类型的调用者不太灵活,但如果调用函数的目的总是很窄,那么可能更简单的代码来集中拒绝捕获。

没有正确/错误的答案。 这主要是您最想优化的内容(灵活的呼叫者使用与集中式错误捕获)以及您的个人风格偏好。

这里的概念是有效的,但这是伪代码,我没有运行它。此外,"引号"中的任何术语都不是正确的术语

在使用 try catch 开发层次结构时要记住的重要一点是,错误总是在三个方面抛出。因此,您应该始终确保在最高级别进行尝试捕获,以便处理错误。

在服务中抛出错误并在路由中处理它的情况下,您的服务最好保留为"纯函数"以赶上更高的功能。我发现这也会在潜意识中让你避免循环依赖。您可以在两个"级别"上使用 try/catch 的情况可能是如果您想抛出自定义错误。

这方面的一个例子是:

// Router
Router.get('/tasks', async (req, res) => {
try { 
return await TodoService.getTasksById(taskId);
} catch (err) {
return errorResponseUtility(err.status, err.message);
}
}
// Service
const getTasksById = async id => {
try {
return await DB()
.connect('tasks')
.select('*')
.where({ id });
} catch (err) {
if(err instanceOf NotFoundError) throw err;
}
}
// Error Defs
const NotFoundError = {
status: 404,
message: 'Resource could not be found'
}

这样,如果您没有任何错误,则可以以正确的方式与随机502

其他:解构你的参数:

const { id: taskId } = req.params; // This renames id to taskId

或者,不要解构它,直接解析它,因为它只使用一次。

不幸的是,在我写完这个答案后,OP 编辑了他们的问题以使其有所不同。 这个答案是写在问题的原始版本上。

两者都工作正常,这真的是一个意见问题,谁会喜欢。

首先,确保方法调用永远不会返回被拒绝的承诺,因此您不必在调用方中捕获拒绝。

在第二个中,您可以返回被拒绝的承诺,因此您必须在调用方中捕获拒绝。

就个人而言,每当我看到一个没有代码的await捕获拒绝时,它看起来就像一个编码错误,所以你必须很好地记录该方法永远不会拒绝。我个人认为,如果错误被拒绝,然后调用者可以决定该怎么做,它会更灵活。 这允许更多种类的调用方使用该方法,但随后确实需要调用方捕获拒绝。

另一方面,如果函数的所有可能用法都在请求处理程序中,而您要做的就是返回一个没有拒绝的状态,以便可以将状态作为响应发送回去,那么您可能通过在函数中集中捕获错误而不是在每个调用方中来保存代码。

就像我说的,这主要是编码风格偏好的问题。 您只需要确保在某处处理任何可能的承诺拒绝,以便始终对传入的请求发送适当的响应。

所以答案取决于代码风格。 Javascript有很多方法来构建代码。 您的第一个选项更接近使用箭头函数的函数样式,第二个选项是使用 ES2015 中引入的类设置。

但是,只有第一个实际创建路由器。 因此,您仍然需要代码将 TodoService 中的静态方法映射到 URL。

来自 Java 的开发人员会发现您的基于类的代码更容易理解。 有快递小服务经验的人可能会发现第一个例子更容易理解。

作为开发人员,我个人的偏好是尽可能轻松地将日志中的 API 调用映射到代码。 我喜欢 Express 的一点是,使用嵌套路由器和中间件非常简单。

所以对我来说,你的第一个很接近。 我只是不会有任何匿名功能。 相反,这些函数应该在外部文件中,也许是routes.js,并且经过设置,以便您可以轻松地对它们进行单元测试。

例如

路线.js

function makeTaskRoute = function(TodoService) {
return async getTasks(req, res, next) => {
const results = await TodoService.getTasks();
return res.status(201).json(results);
}
}
module.exports = {
makeTaskRoute
}

然后在索引中.js

const TodoService = require('todo/service/TodoService.js');
const getTasks = require('./routes/routes.js').makeTaskRoute(TodoService);
const router = express.Router()
router.get('/tasks', getTasks);

这是一种更实用的样式,但也允许依赖注入和轻松测试。

要使它更清洁,还有更多的事情要做,而且很多是偏好。 但我发现这(加上在打字稿中使用声明的类型并干净地分离出数据结构和突变)干净、易于阅读、易于维护和改进。

最新更新