Цикл ожидания внутри 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'
}
Другие вопросы по тегам