Skip to content

Commit

Permalink
timers: introduce setInterval async iterator
Browse files Browse the repository at this point in the history
Added setInterval async generator to timers\promises.
Utilises async generators to provide an iterator compatible with
`for await`.

Co-Authored-By: Fabian Cook <hello@fabiancook.dev>

fix message

PR-URL: #37153
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
Linkgoron authored and danielleadams committed Feb 16, 2021
1 parent bfe0b46 commit 4ebe38b
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 13 deletions.
32 changes: 32 additions & 0 deletions doc/api/timers.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,38 @@ added: v15.0.0
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
cancel the scheduled `Immediate`.

### `timersPromises.setInterval([delay[, value[, options]]])`
<!-- YAML
added: REPLACEME
-->

Returns an async iterator that generates values in an interval of `delay` ms.

* `delay` {number} The number of milliseconds to wait between iterations.
**Default**: `1`.
* `value` {any} A value with which the iterator returns.
* `options` {Object}
* `ref` {boolean} Set to `false` to indicate that the scheduled `Timeout`
between iterations should not require the Node.js event loop to
remain active.
**Default**: `true`.
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
cancel the scheduled `Timeout` between operations.

```js
(async function() {
const { setInterval } = require('timers/promises');
const interval = 100;
for await (const startTime of setInterval(interval, Date.now())) {
const now = Date.now();
console.log(now);
if ((now - startTime) > 1000)
break;
}
console.log(Date.now());
})();
```

[Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout
[`AbortController`]: globals.md#globals_class_abortcontroller
[`TypeError`]: errors.md#errors_class_typeerror
Expand Down
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;
}
}
throw new AbortError();
} finally {
// eslint-disable-next-line no-undef
clearInterval(interval);
signal?.removeEventListener('abort', onCancel);
}
}

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

0 comments on commit 4ebe38b

Please sign in to comment.