如何使用 Express 从异步方法返回有效响应?



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

有3 个参数提供给路由(邮政编码、类型、rad)。 从那里,我使用 NPM 邮政编码包返回提供的 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)
})
});

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 路由器提供的默认 Error 对象进行调用。

我已经在以下实验中展示了这一点:将异步函数传递给 Node.js Express.js 路由器

这意味着您将能够只进行回调async并直接从中使用await,而无需任何额外的包装器:

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

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

与其手动将每个回调函数转换为 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将并行运行承诺。

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

当您不在异步函数中时,不应引发错误。

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

为了补充Steven Spungin的答案,这里有Promise.all重构的getUsers函数:

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.
}

相关内容

最新更新