NodeJS Express 异步/等待

Posted

技术标签:

【中文标题】NodeJS Express 异步/等待【英文标题】:NodeJS Express async/await 【发布时间】:2019-07-20 16:22:51 【问题描述】:

仍然了解 Node 的非阻塞特性。以下代码按预期执行。但是,我想知道是否有更好的方法来完成这项任务。

为路线提供了 3 个参数(邮政编码、类型、弧度)。 从那里我使用 NPM Zipcode 包在提供的 rad 中返回一个邮政编码数组。

然后,我在异步函数中的 zips 数组上使用 for 循环,并等待执行 mysql 查询并返回承诺的函数的响应。然后返回一个用户对象数组。

我的不确定是我是否正确发送了响应,或者是否有更有效的方法来编写此代码。

谢谢。

router.get('/:zipcode/:type/:rad', function (req, res) 

  const rad = req.params.rad;
  const zip = req.params.zipcode;
  let zips = zipcodes.radius(zip, rad);
  zips.push(zip);

  let type;
  if(req.params.type === 'bartenders') 
    type = 0;
   else 
    type = 1;
  

  const params = 
    'type': type,
    'zips': zips
  ;


  function userGroup(type, zip) 
    return new Promise(resolve => 
      connection.query(`SELECT * FROM bt9.users WHERE zip = $zip AND type = $type AND display = 1`, function (err, result) 
        if(err) throw err;
        resolve(result);
      );
    );
  


  async function getUsers(params) 
    let userList = [];
    for (i = 0; i < params.zips.length; i++) 
      const users = await userGroup(params.type, params.zips[i]);
      for (u = 0; u < users.length; u++) 
        userList.push(users[u]);
      
    
    return userList;
  


  function sendUsers(callback) 
    getUsers(params).then( res => 
      callback(null, res)
    )
  


  sendUsers(function(err, result) 
    if(err) throw err;
    res.send(result)
  )


);

【问题讨论】:

【参考方案1】:

Express 5 会自动正确处理异步错误

https://expressjs.com/en/guide/error-handling.html目前说的很清楚:

从 Express 5 开始,返回 Promise 的路由处理程序和中间件将在拒绝或抛出错误时自动调用 next(value)。例如:

app.get('/user/:id', async function (req, res, next) 
 var user = await getUserById(req.params.id)
 res.send(user)
)

如果 getUserById 抛出错误或拒绝,next 将使用抛出的错误或拒绝的值调用。如果没有提供拒绝值,next 将使用 Express 路由器提供的默认错误对象调用。

我在一个实验中证明了这一点:Passing in Async functions to Node.js Express.js router

这意味着您可以直接调用async 并从中直接使用await,而无需任何额外的包装器:

router.get('/:zipcode/:type/:rad', async (req) => 
  ...
  return await getUsers( type, zips );
);

请注意,截至 2021 年 12 月,Express 5 仍处于 alpha 版本,不建议用于生产。

【讨论】:

【参考方案2】:

与其手动将每个回调函数转换为 Promise,不如创建一个包装器来支持 async/await。

参考https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

function asyncWrapper(fn) 
  return (req, res, next) => 
    return Promise.resolve(fn(req))
      .then((result) => res.send(result))
      .catch((err) => next(err))
  

示例代码

async createUser(req) 
  const user = await User.save(req.body)
  return user


router.post('/users', asyncWrapper(createUser))

重构您的代码

function userGroup(type, zip) 
  return new Promise(resolve => 
    connection.query(`SELECT * FROM bt9.users WHERE zip = $zip AND type = $type AND display = 1`, function (err, result) 
      if(err) throw err;
      resolve(result);
    );
  );


async function getUsers( type, zips ) 
  let userList = [];
  // [IMPORTANT]
  // - Replaced the for-loop to for-of-loop for await to work.
  // - This is not efficient because the `userGroup` function is run one by one serially.
  for (let zip of zips) 
    const users = await userGroup(type, zip);
    userList = userList.concat(users);
  
  return userList;


router.get('/:zipcode/:type/:rad', asyncWrapper(async (req) => 
  const rad = req.params.rad;
  const zip = req.params.zipcode;
  let zips = zipcodes.radius(zip, rad);
  zips.push(zip);

  let type;
  if(req.params.type === 'bartenders') 
    type = 0;
   else 
    type = 1;
  

  return await getUsers( type, zips );
));

为了进一步提高效率,您应该将getUsers 中的for-of-loop 替换为bluebird 提供的Promise.mapPromise.map 将并行运行 Promise。

async function getUsers( type, zips ) 
  let userList = []
  const userListList = await Promise.map(zips, (zip) => 
    return userGroup(type, zip);
  );
  for (let users of userListList) 
    userList = userList.concat(users)
  
  return userList;

【讨论】:

【参考方案3】:

要添加到Steven Spungin's answer,这里是getUsersPromise.all 重构的函数:

function getUsers(zips, type) 
  return Promise.all(zips.map(zip => userGroup(type, zip)))
    .then(users => users.flat());

或者,如果您不介意使用第三方模块,您可以使用async-af 并使用其mapAF(别名map)方法:

const aaf = require('async-af');
// ...
async function getUsers(zips, type) 
  const userGroups = await aaf(zips).map(zip => userGroup(type, zip));
  return userGroups.flat(); // flat isn't yet part of 'async-af' but now that it's finalized for ES, I'm sure it'll be added soon.

【讨论】:

【参考方案4】:

当你不在异步函数中时,你不应该抛出错误。

function userGroup(type, zip) 
    return new Promise( (resolve,reject) => 
      connection.query(`SELECT * FROM bt9.users WHERE zip = $zip AND type = $type AND display = 1`, function (err, result) 
        if(err) return reject(err); //<- reject and return
        resolve(result);
      );
    );
  

此外,您可以在每个循环迭代中使用Promise.all 和一组promise,而不是await。这将允许并行执行您的连接。

【讨论】:

以上是关于NodeJS Express 异步/等待的主要内容,如果未能解决你的问题,请参考以下文章

NodeJS Express API 调用结构异步事件

使用异步和请求包(NodeJS / Express)进行多个 API 调用

Nodejs Express新手教程&高手进阶

确认响应发送太晚; NodeJS Express 异步响应

nodejs express 允许跨域设置

window下nodejs环境和express插件的安装