如何将多个响应合并到一个json中

时间:2018-05-22 12:41:54

标签: javascript node.js express cheerio

如何将多个响应合并到一个json中?谢谢!

当我运行代码时,我收到错误:

错误[ERR_HTTP_HEADERS_SENT]:无法在将标头发送到客户端后设置标头

...

var trackArrayReg = [
/^[A-Z]{2}\d{14}NPI$|^460\d{9}$|^959\d{9}$/i,
/^LP0\d{13}$|^[A-Z]{2}\d{14}NPI$/i
];

router.get('/:trackId', function(req, res) {
var trackId = req.params.trackId;

trackArrayReg.forEach(function(item, index, urlsArray) {
    if (trackArrayReg[index].exec(trackId)) {
        track = index;

        request({
            method: config[track].method,
            url: config[track].url + trackId,
            timeout: config[track].timeout,
            maxAttempts: 3,
            retryDelay: 500
        }, function(err, response, body, callback) {
            if (err) return console.error(err);

            $ = cheerio.load(body);

            stat = [];


            $(config[track].response.rowSelector).map(function(i, links) {
                var date = $(links).find(config[track].response.columnSelector).eq(config[track].response.dateColumnIndex).text(),
                    status = $(links).find(config[track].response.columnSelector).eq(config[track].response.stateColumnIndex).text(),
                    location = $(links).find(config[track].response.columnSelector).eq(config[track].response.locationColumnIndex).text();
                stat.push({
                    location: location,
                    date: date,
                    status: status,
                    carrier: track
                });
            });

            var states = JSON.parse(JSON.stringify(stat));

            res.send({states});
          });}});});

...

1 个答案:

答案 0 :(得分:0)

看起来传入请求会触发多个外部请求,这些请求会针对某些统计信息进行解析,这些统计信息最终会被合并,然后包含在对原始传入请求的响应中。如果这是准确的,那么这是处理多个异步请求的问题。

如何解决这个问题需要我们看一下目前正在发生的事情。由于外部请求都是由forEach循环并行有效触发的异步调用,因此首先响应的请求将是触发对原始传入请求的响应的请求。遗憾的是,剩余的外部请求一旦收到响应,也会尝试响应原始的传入请求。因此Express的错误是试图不止一次回应。

有效地,代码目前正在执行此操作(假设为x > y):

incoming req --> external req #1 (t0) --> response (t0+x) --> res.send ❌
             --> external req #2 (t0) --> response (t0+y) --> res.send ✅

注意:外部请求处于竞争状态,因此,首先收到响应的响应将首先响应原始传入请求。

要解决此问题,需要有一种方法来管理异步外部请求,然后组合它们的输出。

通过使用promises可以处理解决方案的第一部分,管理异步请求。由于request已经在代码中使用,因此使用promise版本request-promise是一个简单的转换。 注意:我们还可以使用transform中的request-promise函数来简化通过cheerio进行正文解析。

下一部分是结合外部请求的解析响应。

目前,上述代码并行触发了大量请求。假设这不是问题,我们可以将Promise.all与“地图”一起使用,而不是forEach,等待所有请求回复。

注意:鉴于每个请求似乎都会创建一个值数组,整体结果将是这些值数组的数组。

因此,在我们的流程图中,我们将:

inc req --> p.all(map(ext req #1 (t0) --> res (t0+2n) -->)) --> res.send ✅
                     (ext req #2 (t0) --> res (t0+n)  -->) 

将所有内容放在一起(使用其他一些语法编辑): 注意:这是未经测试的代码。

const requestPromise = require('request-promise-native');
const trackArrayReg = [
  /^[A-Z]{2}\d{14}NPI$|^460\d{9}$|^959\d{9}$/i,
  /^LP0\d{13}$|^[A-Z]{2}\d{14}NPI$/i
];

router.get('/:trackId', ({params: {trackId}}, res) => {
  Promise.all(trackArrayReg.map((regexp, track) => {
    if (regexp.test(trackId)) {
      return requestPromise({
        method: config[track].method,
        url: config[track].url + trackId,
        timeout: config[track].timeout,
        maxAttempts: 3,
        retryDelay: 500,
        transform(body) {
          return cheerio.load(body);
        }
      }).then(($) => {
        const rows = $(config[track].response.rowSelector);

        return rows.map((links) => {
          const columns = $(links).find(config[track].response.columnSelector);
          const date = columns.eq(config[track].response.dateColumnIndex).text();
          const status = columns.eq(config[track].response.stateColumnIndex).text();
          const location = columns.eq(config[track].response.locationColumnIndex).text();

          return {
            location,
            date,
            status,
            carrier: track
          };
        });
      }).catch((err) => {
        console.error(err)
      });
    }
  })).then((stats) => {
    // Combine or modify the stats array as desired
    res.json(stats);
  });
});

对于重构的进一步练习,您还可以使用async / await的语法糖来帮助管理promises。我会把它留作好奇的练习。

希望这有帮助!