timers: introduce setInterval async iterator by Linkgoron · Pull Request #37153 · nodejs/node · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions doc/api/timers.md
58 changes: 57 additions & 1 deletion lib/timers/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const {
codes: { ERR_INVALID_ARG_TYPE }
} = require('internal/errors');

const { validateAbortSignal } = require('internal/validators');
const {
validateAbortSignal,
validateBoolean,
validateObject,
} = require('internal/validators');

function cancelListenerHandler(clear, reject) {
if (!this._destroyed) {
Expand Down Expand Up @@ -111,7 +115,59 @@ function setImmediate(value, options = {}) {
() => signal.removeEventListener('abort', oncancel)) : ret;
}

async function* setInterval(after, value, options = {}) {
validateObject(options, 'options');
const { signal, ref = true } = options;
validateAbortSignal(signal, 'options.signal');
validateBoolean(ref, 'options.ref');

if (signal?.aborted)
throw new AbortError();

let onCancel;
let interval;
try {
let notYielded = 0;
let callback;
interval = new Timeout(() => {
notYielded++;
if (callback) {
callback();
callback = undefined;
}
}, after, undefined, true, true);
if (!ref) interval.unref();
insert(interval, interval._idleTimeout);
if (signal) {
onCancel = () => {
// eslint-disable-next-line no-undef
clearInterval(interval);
if (callback) {
callback(PromiseReject(new AbortError()));
callback = undefined;
}
};
signal.addEventListener('abort', onCancel, { once: true });
}

while (!signal?.aborted) {
if (notYielded === 0) {
await new Promise((resolve) => callback = resolve);
}
for (; notYielded > 0; notYielded--) {
yield value;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If typeof value === 'function', then it would be worthwhile calling the function here. Also, it would be worthwhile making this an await so that if value is a promise the yield happens after the Promise is resolved...

So, something like...

Suggested change

This would allow patterns such as:

async function getValue() { /** ... **/ }

for await (const a of setInterval(100, getValue)) {
  console.log(a);
}

// and

for await (const a of setInterval(100, getValue())) {
  console.log(a);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell
Ideally when this lands (stage 2, so good chance) you could just do setInterval(100).map(getValue) which is kind of neater :]

}
}
throw new AbortError();
} finally {
// eslint-disable-next-line no-undef
clearInterval(interval);
signal?.removeEventListener('abort', onCancel);
}
}

module.exports = {
setTimeout,
setImmediate,
setInterval,
};
Loading