#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
и первый элемент удаляется. Это приведет к сдвигу всего массива. Обычно вы не хотите изменять массив во время итерации по нему. Это можно решить одним из двух способов:
Используйте
for (const data of dataBuffer)
вместо и объединяйте все элементы после завершения итерации.dataBuffer.slice(0)
(очищает массив).Вместо этого используйте цикл 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
, если вы не возражаете.