Все еще осваиваю неблокирующий характер Node. Следующий код выполняется по назначению. Тем не менее, мне интересно, есть ли лучший подход для выполнения задачи.
Маршруту передаются 3 параметра (zipcode, type, rad). Оттуда я использую пакет NPM Zipcode, чтобы вернуть массив почтовых индексов в пределах предоставленного rad.
Затем я использую цикл for для массива zips в асинхронной функции и жду ответа функции, которая выполняет запрос 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 [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы не должны выдавать ошибку, если вы не находитесь внутри асинхронной функции.
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 с массивом промисов вместо await внутри каждой итерации цикла. Это позволит параллельно выполнять ваше соединение.
Чтобы добавить к Ответ Стивена Спунгина, вот функция 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.
}
Вместо того, чтобы вручную преобразовывать каждую функцию обратного вызова в Promise, проще создать оболочку для поддержки async/await.
Reference
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 });
}));Чтобы еще больше повысить эффективность, вы должны заменить цикл for внутри getUsers на Promise.map, предлагаемый синяя птица. Promise.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;
}Express 5 будет автоматически корректно обрабатывать асинхронные ошибки.
https://expressjs.com/en/guide/error-handling.html в настоящее время ясно говорит:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) { var user = await getUserById(req.params.id) res.send(user) })If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
Я показал это в эксперименте по адресу: Передача асинхронных функций маршрутизатору Node.js Express.js.
Это означает, что вы сможете просто сделать обратный вызов async и использовать await из него напрямую без каких-либо дополнительных обёрток:
router.get('/:zipcode/:type/:rad', async (req) => {
...
return await getUsers({ type, zips });
});
Обратите внимание, что по состоянию на декабрь 2021 года Express 5 все еще находится в альфа-версии и не рекомендуется для использования в рабочей среде.