【问题标题】:NodeJS Express async/awaitNodeJS Express 异步/等待
【发布时间】: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)
  })


});

【问题讨论】:

    标签: javascript node.js express async-await


    【解决方案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。这将允许并行执行您的连接。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-11
            • 2018-02-08
            • 2020-03-31
            • 2019-06-30
            • 2019-08-08
            相关资源
            最近更新 更多