Skip to content

Commit

Permalink
Make Poll an async iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
afshin committed Aug 31, 2022
1 parent b603dd3 commit 51fb144
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 5 deletions.
7 changes: 4 additions & 3 deletions packages/polling/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, U, V extends string> {
export interface IPoll<T, U, V extends string>
extends AsyncIterable<IPoll.State<T, U, V>> {
/**
* A signal emitted when the poll is disposed.
*/
Expand Down Expand Up @@ -49,8 +50,8 @@ export interface IPoll<T, U, V extends string> {
*
* #### 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<IPoll<T, U, V>>;

Expand Down
15 changes: 15 additions & 0 deletions packages/polling/src/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
return this._ticked;
}

/**
* Return an async iterator that yields every tick.
*/
async *[Symbol.asyncIterator](): AsyncIterableIterator<IPoll.State<T, U, V>> {
yield this.state;
while (!this.isDisposed) {
try {
await this.tick;
} catch (error) {
/* no-op */
}
yield this.state;
}
}

/**
* Dispose the poll.
*/
Expand Down
65 changes: 65 additions & 0 deletions packages/polling/tests/src/poll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>[] = [];
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<any>[] = [];
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<any>[] = [];
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({
Expand Down
2 changes: 1 addition & 1 deletion packages/polling/tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../../../tsconfigbase",
"compilerOptions": {
"lib": ["DOM", "ES6"],
"lib": ["DOM", "ES2018"],
"outDir": "lib",
"rootDir": "src",
"types": ["chai", "mocha"]
Expand Down
3 changes: 2 additions & 1 deletion review/api/polling.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Debouncer<T = any, U = any, V extends any[] = any[]> extends RateLi
}

// @public
export interface IPoll<T, U, V extends string> {
export interface IPoll<T, U, V extends string> extends AsyncIterable<IPoll.State<T, U, V>> {
readonly disposed: ISignal<this, void>;
readonly frequency: IPoll.Frequency;
readonly isDisposed: boolean;
Expand Down Expand Up @@ -50,6 +50,7 @@ export interface IRateLimiter<T = any, U = any, V extends any[] = any[]> extends

// @public
export class Poll<T = any, U = any, V extends string = 'standby'> implements IObservableDisposable, IPoll<T, U, V> {
[Symbol.asyncIterator](): AsyncIterableIterator<IPoll.State<T, U, V>>;
constructor(options: Poll.IOptions<T, U, V>);
dispose(): void;
get disposed(): ISignal<this, void>;
Expand Down

0 comments on commit 51fb144

Please sign in to comment.