简体   繁体   English

NodeJS中的异步等待(尝试/捕获)

[英]async await (try/catch) in NodeJS

I'm working on a NodeJS API and I was wondering which of the following 2 examples in the ROUTES FILE is best practice.我正在研究 NodeJS API,我想知道ROUTES 文件中的以下 2 个示例中哪一个是最佳实践。 As you can see in one I'm adding the try/catch and in the other I'm not.正如您在一个中看到的那样,我正在添加 try/catch,而在另一个中我没有。 Notice in the Service.js file I added the try/catch as well.请注意,在Service.js文件中,我也添加了 try/catch。 Do I need it in one place only or using it in both places is better?我是只在一个地方需要它还是在两个地方都使用它更好?

ROUTES FILE路线文件

 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);
         }
      };

SERVICE.JS服务.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;
    }
  }
}

In the now edited version of your question, getTasks() can NEVER reject its promise (since you catch any possible rejections and turn them into a resolved promise).在您的问题的现在编辑版本中, getTasks()永远不能拒绝其 promise (因为您发现任何可能的拒绝并将它们变成已解决的承诺)。 Therefore, the caller in your example 2 contains a completely pointless try/catch that is basically dead code that can never be used.因此,示例 2 中的调用者包含一个完全没有意义的try/catch ,它基本上是永远无法使用的死代码。 While it looks like it might be safer code, it's basically wasted code.虽然看起来它可能是更安全的代码,但它基本上是浪费的代码。

Make it absolutely clear in the doc/contract for the function whether it can or cannot reject.在 function 的文档/合同中明确说明它是否可以拒绝。 If it can't, then there's no reason for the caller to attempt to handle a rejection as that is just pointless code.如果不能,那么调用者就没有理由尝试处理拒绝,因为那只是毫无意义的代码。

Personally, I think it's probably more flexible to let errors flow back through a rejected promise since that can be used in more places and combined with other operations more easily.就个人而言,我认为让错误通过被拒绝的 promise 回流可能更灵活,因为它可以在更多地方使用并且更容易与其他操作结合使用。 But, if the ONLY purpose is as a single part of a request handler, then it may save code to centralize the reject catching and turn it into a resolved object that you return like you are now.但是,如果唯一的目的是作为请求处理程序的一个部分,那么它可能会保存代码以集中拒绝捕获并将其转换为解析的 object,然后像现在一样返回。 Less flexible for different types of callers, but perhaps simpler code to centralize the reject catching if the purpose of calling the function is always narrow.对于不同类型的调用者不太灵活,但如果调用 function 的目的总是很窄,则可能更简单的代码来集中拒绝捕获。

There is no right/wrong answer.没有正确/错误的答案。 It's mostly a matter of what you're most trying to optimize for (flexible caller use vs. centralized error catching) and your personal style preference.这主要取决于您最想优化的内容(灵活的调用者使用与集中式错误捕获)和您的个人风格偏好。

Unfortunately, the OP edited their question to make it different AFTER I wrote this answer.不幸的是,在我写下这个答案之后,OP 编辑了他们的问题以使其有所不同。 This answer was written to the original version of the question.此答案已写入问题的原始版本。

Either works fine and it's really a matter of opinion which one will like.两者都可以正常工作,这真的是一个见仁见智的问题。

In the first, you make sure that the method call can NEVER return a rejected promise, therefore you don't have to catch rejections in the caller.首先,您确保方法调用永远不会返回被拒绝的 promise,因此您不必在调用者中捕获拒绝。

In the second, you can have a rejected promise returned so you have to catch rejections in the caller.在第二种情况下,您可以返回一个被拒绝的 promise,因此您必须在调用者中捕获拒绝。

Personally, it looks like a coding error whenever I see an await that has no code that ever catches a rejection so you'd have to well document that the method NEVER rejects.就个人而言,每当我看到一个没有任何代码的await时,它看起来就像一个编码错误,所以你必须很好地记录该方法从不拒绝。 I personally think it's more flexible if errors reject and the caller can then decide what to do.我个人认为如果错误拒绝并且调用者可以决定做什么,它会更灵活。 That allows the method to be used by a wider variety of callers, but then does require the caller to catch rejections.这允许该方法被更广泛的调用者使用,但确实需要调用者捕获拒绝。

On the other hand, if ALL possible uses of the function are in request handlers where all you want to do is have a status returned with no rejections so that status can be sent back as the response, then you're probably saving code by catching errors centrally in the function rather than in every caller.另一方面,如果 function 的所有可能用途都在请求处理程序中,您所要做的就是返回没有拒绝的状态,以便可以将状态作为响应发送回,那么您可能通过捕获来节省代码错误集中在 function 中,而不是在每个调用者中。

Like I said, it's mostly a matter of coding style preference.就像我说的,这主要是编码风格偏好的问题。 You just have to make sure that any possible promise rejection is handled somewhere so you always send an appropriate response to the incoming request.您只需确保在某处处理任何可能的 promise 拒绝,以便始终对传入请求发送适当的响应。

The concepts here are valid but this is pseudo code, I have not run it.这里的概念是有效的,但这是伪代码,我没有运行它。 Additionaly any terms in 'quotes' are not correct terminology另外,“引号”中的任何术语都不是正确的术语

Something important to remember when developing a hierarchy with try catch is that errors are always thrown up the three.使用 try catch 开发层次结构时要记住的重要一点是错误总是抛出三个。 Therefore you should always ensure you have a try catch at the highest level in order to handle errors.因此,您应该始终确保您有一个最高级别的 try catch 以处理错误。

In your case where you might throw an error in a service and handle it in a route, your service would be best left as a 'pure function' to catch higher up.在您可能在服务中引发错误并在路由中处理它的情况下,您的服务最好保留为“纯功能”以赶上更高的水平。 I find this will subconsciously allow you to avoid Circular dependencies as well.我发现这会下意识地让你避免循环依赖 A case where you might use a try/catch on both 'levels' could be if you want to throw custom errors.如果您想抛出自定义错误,您可能会在两个“级别”上使用 try/catch。

An example of that is:一个例子是:

// 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'
}

This way if you don't have anything you can error but in a correct way VS a random 502这样,如果您没有任何错误,但以正确的方式与随机 502

Other: Destructure your req.params:其他:解构你的 req.params:

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

Alternatively, don't destructure it, just parse it straight through since it is only used once.或者,不要解构它,直接解析它,因为它只使用一次。

So the answer depends on code style.所以答案取决于代码风格。 Javascript has a lot of ways to structure code. Javascript 有很多结构代码的方法。 Your first option is closer to a functional style using the arrow function, the second is using the class setup introduced in ES2015.您的第一个选项更接近使用箭头 function 的功能样式,第二个选项是使用 ES2015 中引入的 class 设置。

However, only the first actually creates the router.但是,只有第一个实际创建了路由器。 So you still need code to map your static methods in the TodoService to URLs.因此,您仍然需要将 TodoService 中的 map 您的 static 方法编码到 URL。

Developers coming from Java will find your class based code easier to follow.来自 Java 的开发人员会发现基于 class 的代码更易于遵循。 People with experience doing small services in express may find the first example easier to follow.有在快递中提供小型服务经验的人可能会发现第一个示例更容易理解。

My personal preference as a developer is to make it as easy as possible to map API calls in logs to code.作为开发人员,我个人的偏好是尽可能简单地在日志中调用 map API 调用代码。 One of the things I like about express is how easy this is with nested routers and middleware.我喜欢 express 的一件事是使用嵌套路由器和中间件是多么容易。

So to me, your first is close.所以对我来说,你的第一个很接近。 I just wouldn't have any anonymous functions.我只是没有任何匿名函数。 Instead those functions should be in an external file, maybe routes.js , and are setup so that you can unit test them easily.相反,这些函数应该在一个外部文件中,可能是routes.js ,并且设置好以便您可以轻松地对它们进行单元测试。

eg例如

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
}

Then in index.js然后在 index.js

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

router.get('/tasks', getTasks);

This is a more functional style but also allows dependency injection and easy testing.这是一种更实用的风格,但也允许依赖注入和简单的测试。

There's even more to do to make this cleaner, and a lot is preference.要使这个更清洁,还有更多工作要做,而且很多都是偏好。 But I've found this (plus using declared types in typescript and cleanly separating out data structures and mutations) to be clean, easy to read, and easy to maintain and improve.但我发现这一点(加上在 typescript 中使用声明的类型并干净地分离出数据结构和突变)是干净的、易于阅读的、易于维护和改进的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM