【问题标题】:Node and async: Why is await not being honored in a sub-function?节点和异步:为什么在子功能中不尊重等待?
【发布时间】:2022-01-04 01:23:39
【问题描述】:

我是一名 Node.js 学生,正在编写一个简短的示例。 /org 上的获取按预期工作。

在 /orgg 上,我尝试将服务器“获取数据”逻辑与控制器隔离开来。但是,getRowsAsync() 会立即返回一个 Promise。

这种“将 await 放入 express.get()”不利于隐藏逻辑。如果我的 biz 逻辑需要一些顺序的 Promise,那么 biz 逻辑就必须冒泡到控制器中。

如何在控制器中调用“doBizLogic()”,隐藏我的等待,让控制器等待逻辑完成? 必须我将回调函数传递给 biz 逻辑以使该方案有效吗?

这是我的 index.js。我省略了database.js,但我从https://mhagemann.medium.com/create-a-mysql-database-middleware-with-node-js-8-and-async-await-6984a09d49f4借来的

const express = require("express");
const urlPort = 3000;
let app = express();
let mysql = require("mysql2");
let pool = require("./database");

app.listen(urlPort, () => {
  console.log("Server is running at port " + urlPort);
});

app.get("/", (req, res) => {
  res.send("This app responds to /org and /org/:id only.");
});

app.get("/org", async (req, res) => {
  let rows = await getRows("select * from org");
  // the log always prints a JSON array.
  console.log("in app.get for /org, rows: ", rows);
  res.send(rows);
});

app.get("/orgg", (req, res) => {
  let rows = getRowsAsync();
  // the log() prints immediately prints a Promise.
  console.log("in app.get for /orgg, rows: ", rows);
  res.send(rows);
});

function getRows(sql, params = []) {
  let rows = pool.query(sql, params);
  // the log() prints a Promise.
  console.log("in getRows, rows: ", rows); // returns 
  return rows;
}

async function getRowsAsync() {
  let rows = await getRows("select * from org");
  // the log() prints a JSON array, once it is finally called.
  console.log("in getRowsAsync, rows: ", rows);
  return rows;
}

【问题讨论】:

  • 你需要let rows = await getRowsAsync()
  • 你的函数是同步的还是异步的不是你应该隐藏的“业务逻辑”。它只是函数定义的一部分,与它的参数或返回值没有什么不同。调用者需要知道函数是否是异步的,就像它需要知道要传递多少个参数一样。
  • 好的,我可以接受 this 解决方案。我将“fcn”标记为 app.get("/orgg, async fcn),并在 getRowsAsync() 上等待。然后我可以将任意数量的 async/await 组合埋入任意深度,例如在 getRowsAsync1 中,调用... getRowsAsyncN,DB终于可以工作了。有更好的解决方案吗?

标签: javascript node.js express async-await


【解决方案1】:

您的具体问题

如何在控制器中调用“doBizLogic()”,隐藏我的等待,让控制器等待逻辑完成?

你无法隐藏这个逻辑。任何“可能”是异步的 API 都必须设计为异步 API,并且调用者需要知道如何正确使用异步 API。

您无法隐藏异步性,因为 Javascript 无法将异步操作变为同步操作。如果您的操作的任何部分是异步的,那么它将需要一个异步接口来传达结果和完成(通过承诺、回调或事件)。

我必须向 biz 逻辑传递一个回调函数才能使这个方案起作用吗?

您可以使用回调,但我建议只返回一个承诺,即调用者可以使用await on,因为这是一种比回调更现代的设计,并且更易于编程,尤其是在有其他异步操作的情况下在同一个函数中。调用者必须利用返回的承诺才能正常工作。

所以,让您的来电者使用await。无论如何,调用者必须“知道”它是一个异步接口,因为您使用的异步接口与同步接口不同。

这种“将 await 放入 express.get()”并不适合隐藏逻辑。如果我的 biz 逻辑需要一些顺序的 Promise,那么 biz 逻辑就必须冒泡到控制器中。

是的,这就是 Javascript 中的方式。调用者必须知道他们是在调用同步 API 还是异步 API,以便他们知道如何获取结果/错误和完成。除非您将所有接口都设计为异步的,并将对这些 API 的所有调用都视为异步(这对于完全同步的 API 来说效率不高),否则没有任何办法。

为什么在子函数中没有执行 await?

在这段代码中:

function getRows(sql, params = []) {
  let rows = pool.query(sql, params);
  // the log() prints a Promise.
  console.log("in getRows, rows: ", rows); // returns 
  return rows;
}

代码未正确处理异步pool.query()。该函数显然返回了一个承诺,您无需等待该承诺通过await.then() 解决。因此,当您尝试记录该值时,您只会得到承诺。此函数将返回承诺,因此调用者可以通过在该承诺上使用 .then()await 来获取值。

Await 需要正确的 promise 实现

我还要提到,为了让await 为您的调用者正常工作,您的函数必须返回一个在异步操作完成时解决或拒绝的承诺,并且任何已解决的值或被拒绝的错误也在该承诺中。因此,仅仅在一个没有返回正确连接到异步操作的承诺的函数上打一个await 是行不通的。必须正确实施承诺,await 才能完成其工作。

使用支持承诺的 mysql 版本

由于您使用的是 mysql,我强烈建议您获取为数据库实现 promise 接口的模块,这样您就可以使用 promise 进行所有异步编码,而不是普通的回调。

link 您在 2018 年的一篇文章中提出的问题显示了许多使用普通回调的 mysql 示例。我不建议使用普通回调来启动一个新项目以进行异步操作。一旦开始使用基于 Promise 的接口,您将很快学会欣赏使用基于 Promise 的接口的优势,而 Promise 是在 Javascript 中处理异步操作的现代方式。

您必须捕获所有的承诺拒绝

附:您使用的任何承诺都必须在调用链中的某个位置有一个.catch() 或一个await 和一个try/catch,这将捕获一个潜在的被拒绝的承诺。因此,所有调用异步 API 的快速请求处理程序都缺少正确的错误处理。没有那个,你会得到一个未捕获的 promise 拒绝。充其量,请求处理程序永远不会发送响应。在最坏的情况下,您的服务器将关闭。所有可能被拒绝的 Promise 都必须在代码中的某个地方捕获。

所以,是这样的:

app.get("/org", async (req, res) => {
  let rows = await getRows("select * from org");
  // the log always prints a JSON array.
  console.log("in app.get for /org, rows: ", rows);
  res.send(rows);
});

应该是这样的:

app.get("/org", async (req, res) => {
  try {
    let rows = await getRows("select * from org");
    // the log always prints a JSON array.
    console.log("in app.get for /org, rows: ", rows);
    res.send(rows);
  } catch(e) {
    console.log(e);
    res.sendStatus(500);
  }
});

或者,如果您想集中处理所有错误:

app.get("/org", async (req, res) => {
  try {
    let rows = await getRows("select * from org");
    // the log always prints a JSON array.
    console.log("in app.get for /org, rows: ", rows);
    res.send(rows);
  } catch(e) {
    console.log(e);
    next(e);          // forward to central Express error handler
  }
});

如果您想捕获业务逻辑中的错误以便您可以对其进行处理,但最终仍将错误返回给您的控制器函数,那么您可以catchrethrow

async function getRows(params) {
  try {
      let rows = await pool.query("select * from org", params);
      console.log("in getRows, rows: ", rows);
      return rows;
  } catch(e) {
      // catch the error, log it and do whatever else you want with it
      // either return a value or rethrow some error
      //    usually, you would throw some error here
      //    so a failed operation rejects the promise
      //    this function returns
      console.log(e);
      // you can throw a different error if you want to invent
      // your own specific errors, rather than pass back db errors
      throw e;
  }      
}

【讨论】:

  • jfriend00,这些 cmets 太短,无法适当讨论您的有用建议(谢谢!)但只是一些注释。我确实使用支持承诺的“mysql2”。我的示例 getRows() 针对示例进行了简化——我可以使用您的模型进行错误处理。请注意,许多商务逻辑必须在返回 app.get 之前解决“异步”响应(并在现场处理错误)。例如,从两个 URL 获取数据并合并,然后通过 app.get 返回。因此,我在比 app.get 更深的地方寻求“最佳实践”来解决我的承诺。也许我的(以上)评论中有一种方法。
  • @jerome - 然后,catch 本地在业务逻辑中,并从捕获中重新抛出您希望调用者看到的任何错误。无需重新包装另一个承诺来做到这一点。当出现错误时,业务逻辑需要拒绝,因此您无法完全“处理”业务逻辑中的错误。业务逻辑的调用者必须知道何时出现错误,而拒绝是使用 Promise 时选择错误的机制。关键是您只需返回相同的承诺链,并且您的业务逻辑控制已解决的值或被拒绝的原因。无需包装另一个承诺。
  • @Jerome - 我添加了一个在本地级别捕获错误、执行某些操作、重新抛出的示例,因此承诺保持被拒绝,并作为拒绝传达给调用者。
猜你喜欢
  • 1970-01-01
  • 2021-08-23
  • 1970-01-01
  • 2023-04-04
  • 2018-04-19
  • 2019-04-22
  • 1970-01-01
  • 1970-01-01
  • 2020-01-11
相关资源
最近更新 更多