Цикл ожидания внутри Promise
Представьте, что у нас есть функция асинхронного генератора:
async f * (connection) {
while (true) {
...
await doStuff()
yield value
}
}
Предположим, что эта функция практически бесконечна и выдает нам результаты каких-то асинхронных действий. Мы хотим повторить эти результаты:
for await (const result of f(connection)) {
...
}
Теперь представьте, что мы хотим вырваться из этого цикла, когда закончится какой-то тайм-аут, и навести порядок:
async outerFunc() {
setTimeout(() => connection.destroy(), TIMEOUT_MS)
for await (const result of f(connection)) {
...
if (something) {
return 'end naturally'
}
}
}
Предположить, что
connection.destroy()
завершает выполнение
f
и заканчивает
for-await
петля. Теперь было бы здорово вернуть некоторое значение из
outerFunc
когда мы заканчиваем тайм-аут. Первая мысль оборачивает в :
async outerFunc() {
return await new Promise((resolve, reject) => {
setTimeout(() => {
connection.destroy()
resolve('end by timeout')
}, TIMEOUT_MS)
for await (const result of f(connection)) { // nope
...
if (something) {
resolve('end naturally')
}
}
})
}
Но мы не можем использовать
awaits
внутри
Promise
и мы не можем сделать функцию
async
из-за этого антипаттерна
Вопрос в том, как правильно вернуться по тайм-ауту?
2 ответа
Это становится намного проще, если вы используете существующую библиотеку, которая может автоматически обрабатывать асинхронные генераторы и тайм-ауты. В приведенном ниже примере для этого используется библиотека iter-ops :
import {pipe, timeout} from 'iter-ops';
// test async endless generator:
async function* gen() {
let count = 0;
while (true) {
yield count++; // endless increment generator
}
}
const i = pipe(
gen(), // your generator
timeout(5, () => {
// 5ms has timed out, do disconnect or whatever
})
); //=> AsyncIterable<number>
// test:
(async function () {
for await(const a of i) {
console.log(a); // display result
}
})();
Предположим, что
connection.destroy()
завершает выполнениеf
и заканчиваетfor-await
петля.
В этом случае просто поместитеreturn
оператор так, чтобы он выполнялся, когда цикл заканчивается:
async outerFunc() {
setTimeout(() => {
connection.destroy()
}, TIMEOUT_MS)
for await (const result of f(connection)) {
...
if (something) {
return 'end naturally'
}
}
return 'end by timeout'
}