【问题标题】:How do I send multiple queries from one endpoint with Express?如何使用 Express 从一个端点发送多个查询?
【发布时间】:2017-09-05 16:36:45
【问题描述】:

我正在尝试多次查询我的数据库并构建一个对象,该对象将来自我的数据库的每个响应存储在一个字段中。这是我的代码:

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 是异步的,所以它不能像我希望的那样工作。如何重建此端点以进行多个数据库调用(来自同一个集合)并返回包含它的对象?

【问题讨论】:

标签: javascript node.js rest express


【解决方案1】:

有不止一种方法可以做到这一点:

嵌套回调

如果没有 promise,你可以嵌套回调:

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#parallel

但这可能仍然不是最方便的方法。

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 关键字创建的函数内部使用它。有关详细信息,请参阅:

有关浏览器的支持,请参阅:

有关 Node 中的支持,请参阅:

在你没有原生支持 asyncawait 的地方你可以使用 Babel:

或者在语法稍有不同的情况下使用基于生成器的方法,例如 co 或 Bluebird 协程:

查看这些答案了解更多信息:

【讨论】:

  • 我要打大约 30 个不同的电话;它们不仅是有条件的,而且会有一个嵌套的噩梦。我很欣赏你的方法,但是,它不够有效。我正在尝试获取我网站上可用的分面搜索功能的参数。
  • @Moshe 复杂代码最灵活的解决方案是异步/等待或基于生成器的协程。请参阅我的更新答案。
【解决方案2】:

你可以用 Promises 做到这一点

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 请求并使用 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 
    });

【讨论】:

  • 你能解释一下 results[0] 和 results[1] 之间的区别吗?它是否遵循.. results[2] ... results[n] for n 个调用?
  • @Moshe 当然。 Promise.all 函数中有一个 Promise 数组。因此,在.thenPromise.all 处理程序中,您有一个参数数组,以与输入数组中相同的顺序传递给输入数组的每个Promise 中的resolve 回调
  • 感谢您的澄清!
猜你喜欢
  • 2021-03-05
  • 1970-01-01
  • 2020-07-03
  • 1970-01-01
  • 2022-12-04
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多