Как объединить несколько ответов в один JSON - PullRequest
0 голосов
/ 22 мая 2018

Как объединить несколько ответов в один 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 голосов
/ 22 мая 2018

Похоже, что входящий запрос запускает несколько внешних запросов, которые анализируются для некоторой статистической информации, которая в конечном итоге должна быть объединена, а затем включена в ответ на исходный входящий запрос.Если это верно, то это проблема с обработкой нескольких асинхронных запросов.

Чтобы решить эту проблему, мы должны посмотреть, что происходит в данный момент.Поскольку все внешние запросы - это асинхронные вызовы, которые фактически запускаются параллельно с помощью цикла forEach, то любой запрос, который первым ответит, будет тем, который инициирует ответ на исходный входящий запрос.К сожалению, оставшиеся внешние запросы также попытаются ответить на исходный входящий запрос, как только они получат ответ самостоятельно.Отсюда ошибка Express о попытке ответить более одного раза.

Фактически код в настоящее время делает это (предполагая x > y):

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

Примечание: внешние запросынаходятся в состоянии состязания, поэтому любой, кто получит ответ первым, сначала ответит на исходный входящий запрос.

Чтобы обойти эту проблему, должен быть способ управления асинхронными внешними запросами, а затемобъедините их выводы.

Обращение к первой части решения, управление асинхронными запросами, может быть обработано с помощью обещаний.Так как request уже используется в коде, то это простой переход к использованию версии обещания: 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 дляпомочь с выполнением обещаний.Я оставлю это как упражнение для любопытных.10

Надеюсь, это поможет!

...