Как отправить несколько запросов из одной конечной точки с помощью Express?

Я пытаюсь сделать запрос к моей базе данных несколько раз и создать объект, который хранит каждый ответ из моей базы данных в поле. Вот мой код:

router.post('/search', (req, res) => {
    var collection = db.get().collection('styles')
    var data = [];

    collection.distinct('make.name', (err, docs) => {
      data.push({'make': docs });
    });

    collection.distinct('model', (function (err, docs) {
        data.push({'model': docs });
    }))

    res.send(data);
});

Поскольку NodeJS/Express является асинхронным, это не работает так, как мне бы хотелось. Как я могу восстановить эту конечную точку, чтобы сделать несколько вызовов базы данных (из одной коллекции) и вернуть объект, содержащий ее?

2 ответа

Решение

Есть несколько способов сделать это:

Вложенные обратные вызовы

Без обещаний вы можете вкладывать обратные вызовы:

router.post('/search', (req, res) => {
    var collection = db.get().collection('styles')
    var data = [];

    collection.distinct('make.name', (err, docs) => {
      if (err) {
        // ALWAYS HANDLE ERRORS!
      }
      data.push({'make': docs });
        collection.distinct('model', (function (err, docs) {
          if (err) {
            // ALWAYS HANDLE ERRORS!
          }
          data.push({'model': docs });
          res.send(data);
        }))
    });
});

Это был бы самый простой способ, но учтите, что он неэффективен, если эти два запроса могут выполняться параллельно.

async модуль

Вы можете использовать async модуль:

router.post('/search', (req, res) => {
    var collection = db.get().collection('styles')
    var data = [];

    async.parallel({
      make: cb => collection.distinct('make.name', cb),
      model: cb => collection.distinct('model', cb),
    }, (err, responses) => {
      if (err) {
        // ALWAYS HANDLE ERRORS!
      }
      data.push({'make': responses.make });
      data.push({'model': responses.model });
      res.send(data);
    });
});

Смотрите: https://caolan.github.io/async/docs.html

Но это все еще не самый удобный метод.

ES2017 async/await

Наиболее гибкий способ сделать это, если у вас есть 30 звонков, это:

  1. Используйте функции, которые возвращают обещания вместо функций, которые принимают обратные вызовы
  2. Используйте async / await, если можете, или хотя бы сопрограммы на основе генератора
  3. Ожидание обещаний (или выдача обещаний), когда логика должна выполняться последовательно
  4. использование Promise.all() для всего, что может быть сделано параллельно

С помощью async / await ваш код может выглядеть так:

    // in sequence:    
    var make = await collection.distinct('make.name');
    var model = await collection.distinct('model');
    // use 'make' and 'model'

Или же:

    // in parallel:
    var array = await Promise.all([
      collection.distinct('make.name'),
      collection.distinct('model'),
    ]);
    // use array[0] and array[1]

Большое преимущество async/await это обработка ошибок:

try {
  var x = await asyncFunc1();
  var array = await Promise.all([asyncFunc2(x), asyncFunc3(x)]);
  var y = asyncFunc4(array);
  console.log(await asyncFunc5(y));
} catch (err) {
  // handle any error here
}

Вы можете использовать его только внутри функции, созданной с помощью async ключевое слово. Для получения дополнительной информации см.:

Для поддержки в браузерах, смотрите:

Для поддержки в узле см.:

В местах, где у вас нет собственной поддержки async а также await Вы можете использовать Babel:

или с немного другим синтаксисом подход на основе генератора, как в co или сопрограммы Bluebird:

Смотрите эти ответы для получения дополнительной информации:

Вы можете сделать это с обещаниями

router.post('/search', (req, res) => {
    var collection = db.get().collection('styles');
    // Create promise for "make.name" query
    let firstQuery = new Promise((resolve, reject) => {
        collection.distinct('make.name', (err, docs) => {
            if (!err) {
                resolve(docs);
            } else {
                reject(err);
            }
        });
    });
    // Create promise for "model" query
    let secondQuery = new Promise((resolve, reject) => {
        collection.distinct('model', (function (err, docs) {
            if (!err) {
                resolve(docs);
            } else {
                reject(err);
            }
        }))
    })
    // Run both queries at the same time and handle both resolve results or first reject
    Promise.all([firstQuery, secondQuery])
        .then((results) => {
            res.send({ "make.name": results[0], "model": results[1] });
        })
        .catch((err) => {
            // Catch error 
            res.send({});
        });
});

Также вы можете использовать деструктурирование в таких функциях обратного вызова:

Promise.all([firstQuery, secondQuery])
    .then(([makeName, model]) => res.send({ "make.name": makeName, model }))

UPD: если у вас есть куча коллекций для запроса, вы можете создать массив имен коллекций, сопоставить его с запросами на обещание и обработать, например, с помощью Promise.all

let collections = ["firstCollection", "secondCollection", "nCollection"];
let promises = collections.map((collectionName) => {
    return new Promise((resolve, reject) => {
        collection.distinct(collectionName, (err, docs) => {
            if (!err) {
                resolve(docs)
            } else {
                reject(err);
            }
        });
    })
});
Promise.all(promises)
    .then(results => {
        // Do what you want to do
    })
    .catch(error => {
        // or catch 
    });
Другие вопросы по тегам