NodeJS Express асинхронно/ожидание

Все еще осваиваю неблокирующий характер 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)
  })


});
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
8
0
11 570
4

Ответы 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 с массивом промисов вместо 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 все еще находится в альфа-версии и не рекомендуется для использования в рабочей среде.

Другие вопросы по теме