From 51fb1448b9d8de4320e0417a110b252d3407b435 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Wed, 31 Aug 2022 09:17:54 +0100 Subject: [PATCH] Make `Poll` an async iterator --- packages/polling/src/index.ts | 7 +-- packages/polling/src/poll.ts | 15 ++++++ packages/polling/tests/src/poll.spec.ts | 65 +++++++++++++++++++++++++ packages/polling/tests/tsconfig.json | 2 +- review/api/polling.api.md | 3 +- 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/packages/polling/src/index.ts b/packages/polling/src/index.ts index af44b6c89..c6db774ac 100644 --- a/packages/polling/src/index.ts +++ b/packages/polling/src/index.ts @@ -18,7 +18,8 @@ export { Debouncer, RateLimiter, Throttler } from './ratelimiter'; * * @typeparam V - The type to extend the phases supported by a poll. */ -export interface IPoll { +export interface IPoll + extends AsyncIterable> { /** * A signal emitted when the poll is disposed. */ @@ -49,8 +50,8 @@ export interface IPoll { * * #### Notes * Usually this will resolve after `state.interval` milliseconds from - * `state.timestamp`. It can resolve earlier if the user starts or refreshes the - * poll, etc. + * `state.timestamp`. It can resolve earlier if the user starts or refreshes + * the poll, etc. */ readonly tick: Promise>; diff --git a/packages/polling/src/poll.ts b/packages/polling/src/poll.ts index 6e0a6ca0e..a794659b2 100644 --- a/packages/polling/src/poll.ts +++ b/packages/polling/src/poll.ts @@ -153,6 +153,21 @@ export class Poll return this._ticked; } + /** + * Return an async iterator that yields every tick. + */ + async *[Symbol.asyncIterator](): AsyncIterableIterator> { + yield this.state; + while (!this.isDisposed) { + try { + await this.tick; + } catch (error) { + /* no-op */ + } + yield this.state; + } + } + /** * Dispose the poll. */ diff --git a/packages/polling/tests/src/poll.spec.ts b/packages/polling/tests/src/poll.spec.ts index d700cb9e7..84685e6a1 100644 --- a/packages/polling/tests/src/poll.spec.ts +++ b/packages/polling/tests/src/poll.spec.ts @@ -195,6 +195,71 @@ describe('Poll', () => { }); }); + describe('#[Symbol.asyncIterator]', () => { + it('should yield after each tick', async () => { + const total = 15; + let i = 0; + poll = new Poll({ + auto: false, + factory: () => (++i > total ? poll.stop() : Promise.resolve()), + frequency: { interval: Poll.IMMEDIATE }, + name: '@lumino/polling:Poll#[Symbol.asyncIterator]-1' + }); + const expected = `started ${'resolved '.repeat(total)}stopped`; + const ticker: IPoll.Phase[] = []; + void poll.start(); + for await (const state of poll) { + ticker.push(state.phase); + if (state.phase === 'stopped') { + break; + } + } + expect(ticker.join(' ')).to.equal(expected); + }); + + it('should yield rejections', async () => { + const total = 11; + let i = 0; + poll = new Poll({ + auto: false, + factory: () => (++i > total ? poll.stop() : Promise.reject()), + frequency: { interval: Poll.IMMEDIATE }, + name: '@lumino/polling:Poll#[Symbol.asyncIterator]-2' + }); + const expected = `started ${'rejected '.repeat(total)}stopped`; + const ticker: IPoll.Phase[] = []; + void poll.start(); + for await (const state of poll) { + ticker.push(state.phase); + if (state.phase === 'stopped') { + break; + } + } + expect(ticker.join(' ')).to.equal(expected); + }); + + it('should yield disposal', async () => { + const total = 2; + let i = 0; + poll = new Poll({ + auto: false, + factory: () => Promise.resolve(++i > total ? poll.dispose() : void 0), + frequency: { interval: Poll.IMMEDIATE }, + name: '@lumino/polling:Poll#[Symbol.asyncIterator]-3' + }); + const expected = `started ${'resolved '.repeat(total)}disposed`; + const ticker: IPoll.Phase[] = []; + void poll.start(); + for await (const state of poll) { + ticker.push(state.phase); + if (poll.isDisposed) { + break; + } + } + expect(ticker.join(' ')).to.equal(expected); + }); + }); + describe('#tick', () => { it('should resolve after a tick', async () => { poll = new Poll({ diff --git a/packages/polling/tests/tsconfig.json b/packages/polling/tests/tsconfig.json index f3cc07db3..64f5412af 100644 --- a/packages/polling/tests/tsconfig.json +++ b/packages/polling/tests/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfigbase", "compilerOptions": { - "lib": ["DOM", "ES6"], + "lib": ["DOM", "ES2018"], "outDir": "lib", "rootDir": "src", "types": ["chai", "mocha"] diff --git a/review/api/polling.api.md b/review/api/polling.api.md index 931c97611..13bfd6c6d 100644 --- a/review/api/polling.api.md +++ b/review/api/polling.api.md @@ -15,7 +15,7 @@ export class Debouncer extends RateLi } // @public -export interface IPoll { +export interface IPoll extends AsyncIterable> { readonly disposed: ISignal; readonly frequency: IPoll.Frequency; readonly isDisposed: boolean; @@ -50,6 +50,7 @@ export interface IRateLimiter extends // @public export class Poll implements IObservableDisposable, IPoll { + [Symbol.asyncIterator](): AsyncIterableIterator>; constructor(options: Poll.IOptions); dispose(): void; get disposed(): ISignal;