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.map
。 Promise.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,这里是getUsers
用Promise.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 异步/等待的主要内容,如果未能解决你的问题,请参考以下文章