4

Without using the Streams API, is it possible to wrap a setInterval in an async generator function, to simulate a never-ending stream?

I know how to do it using setTimeout to supply the delay.

Using setTimeout:

const wait = (delay = 500) => new Promise((resolve) => setTimeout(resolve, delay))

async function * countUp(count = 0) {           
    while(1) (await wait(), yield count++)           
}

(async ()=> {
    for await(let el of countUp())
        console.log(el)
})()

2
  • You could, but I don't think it would be as tidy as using setTimeout as a promise delay. Commented Mar 10, 2020 at 18:48
  • @52d6c6af fair enough, i guess your question is an exception to the rule. Commented Mar 11, 2020 at 21:17

2 Answers 2

1

No. Or: not easily. Async generator functions are promise based, and promises don't deal well with repetitive callbacks. Also setInterval does not support any form of backpressure.

Of course you can implement the streams API yourself, which comes down to making a queue of promises. You can take the code from that answer and write

function countUp(count = 0) {
    const q = new AsyncBlockingQueue()
    setInterval(() => {
        q.enqueue(count++)
    }, 500)
    return q // has a Symbol.asyncIterator method
}

(async() => {
    for await (const el of countUp())
        console.log(el)
})()
Sign up to request clarification or add additional context in comments.

2 Comments

So the answer is actually "yes", but it is much more difficult?
Not just more difficult, but also more inefficient and possibly incorrect if the consumer doesn't keep up with the rate of production
1

Here is an implementation without dependencies. Using Promise.withResolvers() (ECMAScript 2024), we can maintain a queue of deferred objects. This overcomes the problem of resolving a promise that was created in one callback execution in another:

async function* countUp(count = 0) {           
    const deferreds = [Promise.withResolvers()];
    setInterval(() => {
        deferreds.at(-1).resolve(count++);
        deferreds.push(Promise.withResolvers());
    }, 500);
    while (true) {
        yield await deferreds[0].promise;
        deferreds.shift();
    }
}

(async () => {
    for await (const el of countUp()) {
        console.log(el);
    }
})();

If the consumption of the async iterator happens without other "awaits", the queue will have at most two elements.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.