функции async / await имеют странное поведение

#javascript #node.js #async-await

#javascript #node.js #async-await

Вопрос:

У меня есть приложение nodejs / javascript, и у меня есть некоторые проблемы с async / await. Я проверил многие из приведенных здесь предложений, но не могу найти ни одного, который соответствует моей проблеме. Если он есть, я заранее приношу извинения.

У меня есть функция, которая получает информацию из потока websocket. Эта информация буферизуется и ожидает дальнейшей обработки.

Функция, которая обрабатывает информацию из потока (updateOrderBook), вызывается каждую секунду, используя setTimeout .

Чтобы иметь возможность обрабатывать потоковые данные, должна существовать книга заказов. Если этого не произойдет, он должен быть создан. Эта функция (createOrderBook) должна получать данные из REST API, и вот тут у меня проблемы.

В updateOrderBook я ожидаю функции createOrderBook. createOrderBook, в свою очередь, ожидает, что axios вернет данные REST API. Но во время ожидания этих данных API кажется, что управление возвращается обратно в updateOrderBook.

Итак, что я здесь упускаю из виду? и что еще более важно, как я могу это исправить?

 const updateOrderBook = () => {
  // select the coin at hand
  dataBuffer.forEach(async (data, index) => {
    dataBuffer.splice(index, 1);
    let symbol = data.s.toLowerCase();

    if (orderBook.filter(orders => orders.coin === symbol).length === 0) {
      await createOrderBook(symbol);
      console.log(`${symbol}: Orderbook created`);
    }

    if (typeof lastUpdateId[symbol] == "undefined") {
      console.log(
        symbol,
        "UNDEFINED",
        orderBook.filter(orders => orders.coin === symbol).length === 0
      );
    }
    ...
  setTimeout(() => updateOrderBook(), 1000);
};
  
 const createOrderBook = async (symbol) => {
  console.log(symbol, "ENTERED COB");
  orderBook.push({ coin: symbol, bids: [], asks: [] });
  firstEvent[symbol] = true;
  try {
    const response = await axios.get(
      `https://www.binance.com/api/v3/depth?symbol=${symbol.toUpperCase()}amp;limit=1000`
    );
    console.log(symbol, "BEFORE ENTERING HIO");
    handleInitialOrderBook(response, symbol.toLowerCase());
    console.log(symbol, "COB");
  } catch (error) {
    console.log(symbol, error);
  }
};
  

Ожидаемый журнал:

 xrpusdt BEFORE ENTERING HIO
xrpusdt HIO
xrpusdt COB
xrpusdt: Orderbook created
  

Фактический журнал:

 xrpusdt ENTERED COB
xrpusdt UNDEFINED false
xrpusdt BEFORE ENTERING HIO
xrpusdt HIO
xrpusdt COB
xrpusdt: Orderbook created
  

Как вы можете видеть НЕОПРЕДЕЛЕННЫЙ журнал, который, как я ожидаю, произойдет, если вообще произойдет, после завершения createOrderBook, прежде чем createOrderBook будет полностью завершен

Комментарии:

1. Похоже, что буфер данных содержит некоторые данные, которые не проходят условие фильтра после первой итерации. Таким образом, асинхронная if синхронизация выполняется как ожидалось, а затем if выполняется синхронизация и для создания условия гонки, которое вы видите.

2. @RandyCasburn — Я пока не понимаю, можете ли вы объяснить более подробно?

Ответ №1:

Проблема заключается в:

 dataBuffer.forEach(async (data, index) => {
  // ...
});
  

Хотя функция обратного вызова является асинхронной функцией. forEach не проверяет возвращаемое значение этого обратного вызова и не ожидает возвращенного обещания перед вызовом следующей итерации.

В настоящее время первый элемент в буфере ожидает createOrderBook завершения, в то время как второй элемент пропускает if-блок:

 if (orderBook.filter(orders => orders.coin === symbol).length === 0)
  

Это связано с тем, что createOrderBook вызовы orderBook.push(...) перед ожиданием, таким образом, элемент уже отправлен, и движок JavaScript перейдет к следующему элементу, forEach как только это потребуется await createOrderBook .

Итак, как вы это решаете? Самым простым способом было бы не использовать forEach вызов, а вместо этого использовать for...of . Поскольку вы не создаете новые функции обратного вызова, основная функция будет ожидать выполнения обещания и двигаться дальше только после его разрешения.

 for (const [index, data] of dataBuffer.entries()) {
  // ...
}
  

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

Примечание: при использовании dataBuffer.splice(index, 1) внутри цикла вы получаете какое-то странное поведение. На первом элементе index есть 0 и первый элемент удаляется. Это приведет к сдвигу всего массива. Обычно вы не хотите изменять массив во время итерации по нему. Это можно решить одним из двух способов:

  1. Используйте for (const data of dataBuffer) вместо и объединяйте все элементы после завершения итерации. dataBuffer.slice(0) (очищает массив).

  2. Вместо этого используйте цикл while.

     while (dataBuffer.length > 0) {
      const data = dataBuffer.shift();
      // ...
    }
      

 const letters = ["a", "b", "c", "d", "e", "f", "g"];

for (const [index, letter] of letters.entries()) {
  console.log(index, letter);
  letters.splice(index, 1);
}

console.log(letters);  


 const updateOrderBook = async () => {
  // select the coin at hand
  while (dataBuffer.length > 0) {
    const data = dataBuffer.shift();
    let symbol = data.s.toLowerCase();

    if (orderBook.filter(orders => orders.coin === symbol).length === 0) {
      await createOrderBook(symbol);
      console.log(`${symbol}: Orderbook created`);
    }

    if (typeof lastUpdateId[symbol] == "undefined") {
      console.log(
        symbol,
        "UNDEFINED",
        orderBook.filter(orders => orders.coin === symbol).length === 0
      );
    }
  }

  // ...
  setTimeout(() => updateOrderBook(), 1000);
};
  

Комментарии:

1. @MPL Просто чтобы убедиться, что я добавил блок кода о том, как должен выглядеть результат. Не могли бы вы сопоставить это с внесенными вами изменениями? Приведенный выше код никогда не должен регистрироваться "UNDEFINED" ранее "Orderbook created" . Если он все еще это делает, возможно, что-то еще не так.

2. Я использовал простой старый цикл while — который работал. Также попробую ваше решение. 2 дополнительных вопроса: можно ли объединить буфер данных и могу ли я использовать continue для перехода к следующей итерации?

3. @MPL Я обновил ответ важным примечанием. Изменение массива (удаление элементов) во время итерации может привести к странному поведению, поскольку все остальные элементы начинают смещаться.

4. Это решение, которое я тоже придумал. Я просто делаю DataBuffer.shift() И в конце цикла задержка 0 мс, чтобы убедиться, что он не будет работать вечно. возможно, поток добавляется в буфер асинхронно.

5. @MPL shift , вероятно, лучше. Я использовал splice , потому что вы уже используете его, я также обновлю свой ответ, чтобы использовать shift , если вы не возражаете.