Асинхронизация / ожидание с forEach и axios - PullRequest
0 голосов
/ 13 октября 2018

Я застрял в проблеме, и мне нужна ваша помощь.Мне нужно использовать и API для авторегистрации серверов в программном обеспечении с именем rudder.Для достижения моей цели я должен использовать 2 http запроса:

  1. (GET) получить весь сервер с ожидающим статусом
  2. (POST) зарегистрировать мой сервер

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

Вот мой код (/src/controllers/registration.js):

async function index(req, res) {
  // Check parameter
  if (!req.body.hostname) {
    return res.status(422).json({ result: 'error', message: 'Missing hostname parameter' });
  }

  try {
    const pendingNodes = await axios.get(`${config.rudderURL}/rudder/api/latest/nodes/pending?include=minimal`, { headers });
    Object.keys(pendingNodes.data.data.nodes).forEach((key) => {
      // Search in all pending machine if our node is present
      const node = pendingNodes.data.data.nodes[key];
      if (req.body.hostname === node.hostname) {
        // Registration
        const response = async axios.get(`${config.rudderURL}/rudder/api/nodes/pending/${node.id}`, { headers });
        // console.log(response);
        return res.status(200).json({ result: 'success', message: 'Host added to rudder' });
      }
    });
  } catch (err) {
    return res.status(500).json({ result: 'error', message: `${err}` });
  }

  return res.status(500).json({ result: 'error', message: 'Host not found' });
}

Как видите, я использую Object.keys(...).forEach(...) для итерации по моему первому запросу.

Для вашей информации, ответ на первый запрос выглядит примерно так:

{
    "action": "listPendingNodes",
    "result": "success",
    "data": {
        "nodes": [
            {
                "id": "f05f2bde-5416-189c-919c-954acc63dce7",
                "hostname": "foo",
                "status": "pending"
            },
            {
                "id": "b7f597ef-2b46-4283-bf70-d5e8cd84ba86",
                "hostname": "bar",
                "status": "pending"
            }
        ]
    }
}

Мне нужно перебрать этот вывод и сравнить строку имени хоста каждого nodes с моим вводом (простой запрос поста от сценария или почтальона).

Если 2 строки равны, мой второй запросзапустите и должны зарегистрировать мой хост на сервере (запрос здесь упрощен, но вы его получили).

Моя проблема в том, что я не могу найти способ заставить второй запрос работать.Я пробую разные решения и читаю на каком-то форуме и веб-сайте, что сложно работать с loop и async / await одновременно, но у вас есть идея для меня?

Когда я пробую свой код, я получаюсообщение об ошибке:

Ошибка [ERR_HTTP_HEADERS_SENT]: невозможно установить заголовки после их отправки клиенту

Насколько я понял, forEach(...) не ждет моегорезультат второго запроса.Так что return res.status(200).json({ result: 'success', message: 'Host added to rudder' }); называется тогда еще одним, вероятно return res.status(500).json({ result: 'error', message: 'Host not found' });.Я прав?

Я имею в виду, реальная трудность (или нет?) Это Object.keys(...).forEach(...).Может быть, есть простой способ не использовать эту функцию, а другую?Или рефакторинг моего кода?

С уважением.

Ответы [ 2 ]

0 голосов
/ 13 октября 2018

Полученная ошибка

Ошибка [ERR_HTTP_HEADERS_SENT]: Невозможно установить заголовки после их отправки клиенту

Это потому, что вы звоните res.status(200) несколько раз(Вы помещаете это в цикл forEach).Этот ответ должен быть выполнен только один раз.

Я копался в решении и решил использовать reduce, map и Promise.all

async function index(req, res) {
  // Check parameter
  ...

  try {
    const pendingNodes = await axios.get(`${config.rudderURL}/rudder/api/latest/nodes/pending?include=minimal`, { headers });

    // we get node ids in array with hostname is matched with req.body.hostname
    const nodeIdsToRegister = pendingNodes.data.data.nodes.reduce((result, node) => {
      return req.body.hostname === node.hostname ? [...result, node.id] : result;
    }, [])

    // use `Promise.all` to register all node Ids we got previously
    const registers = await Promise.all(nodeIdsToRegister.map(nodeId => axios.get(`${config.rudderURL}/rudder/api/nodes/pending/${node.id}`, { headers })));

    res.status(200).json({ result: 'success', message: 'Host added to rudder' });

  } catch (err) {
    return res.status(500).json({ result: 'error', message: `${err}` });
  }

  return res.status(500).json({ result: 'error', message: 'Host not found' });
}
0 голосов
/ 13 октября 2018

Асинхронное ожидание не работает в цикле forEach.Преобразуйте ваш forEach в for цикл.Примерно так:

let nodes = Object.keys(pendingNodes.data.data.nodes;
for (let i = 0; i < nodes.length; i++) {
    // Perform asynchronous actions and await them, it will work
    ...
}

Подробнее здесь: Использование async / await с циклом forEach

Кроме того, функция является асинхронной, в которой вы можете ожидать несколькозаявления.Вы написали async перед axios.get, который в основном возвращает обещание.Вы должны ожидать этого.

Измените следующее с

const response = async axios.get(`${config.rudderURL}/rudder/api/nodes/pending/${node.id}`, { headers });

на

const response = await axios.get(`${config.rudderURL}/rudder/api/nodes/pending/${node.id}`, { headers });
...