Skip to content

Commit

Permalink
refactor: improve re-exports
Browse files Browse the repository at this point in the history
  • Loading branch information
motss committed May 5, 2021
1 parent 71b7402 commit 974670b
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 137 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"./utc-date/*": "./dist/utc-date/*",
"./utc-time": "./dist/utc-time/index.js",
"./utc-time/*": "./dist/utc-time/*",
"./lib": "./dist/lib/index.js",
"./lib/*": "./dist/lib/*",
"./lib/clone-deep": "./dist/lib/clone-deep.js",
"./lib/parse5": "./dist/lib/parse5.js",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/polling-observer/polling-measure.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PollingMeasure } from '../../polling-observer/polling-measure.js';
import { PollingMeasure } from '../../polling-observer/index.js';
import './setup.js';

it(`instantiates 'PollingMeasure'`, () => {
Expand Down
3 changes: 1 addition & 2 deletions src/__tests__/polling-observer/polling-observer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { PollingObserver } from '../../polling-observer/index.js';
import type { OnfinishFulfilled } from '../../polling-observer/index.js';
import type { PollingMeasure } from '../../polling-observer/polling-measure.js';
import type { OnfinishFulfilled, PollingMeasure } from '../../polling-observer/index.js';
import './setup.js';
import type { MockData } from './test_typings.js';

Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './calendar/calendar.js';
export * from './calendar/index.js';
export * from './deep-clone/index.js';
export * from './delay-until/index.js';
export * from './fetch-as/index.js';
export * from './lib/index.js';
export * from './lit-ntml/index.js';
export * from './normalize-diacritics/index.js';
export * from './polling-observer/index.js';
Expand Down
3 changes: 3 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './clone-deep.js';
export * from './parse5.js';
export * from './prismjs.js';
135 changes: 2 additions & 133 deletions src/polling-observer/index.ts
Original file line number Diff line number Diff line change
@@ -1,133 +1,2 @@
import { delayUntil } from '../delay-until/index.js';
import { globalPerformance } from './global-performance.js';
import { PollingMeasure } from './polling-measure.js';

export interface PollingObserverOptions {
timeout?: number;
interval?: number;
}
type PollingCallback<T> = () => T | Promise<T>;
type ConditionCallback<T> = (
data: T | null | undefined,
records: PollingObserver<T>['_records'],
object: PollingObserver<T>
) => boolean | Promise<boolean>;

export interface OnfinishFulfilled<T> {
status: 'finish' | 'timeout';
value: T | null | undefined;
}
export interface OnfinishRejected {
status: 'error';
reason: Error;
}
export type OnfinishValue<T> = OnfinishFulfilled<T> | OnfinishRejected;
type OnfinishCallback<T> = (
value: OnfinishValue<T>,
records: PollingObserver<T>['_records'],
object: PollingObserver<T>
) => unknown;

function isPromise<T>(r: T | Promise<T>): r is Promise<T> {
return 'function' === typeof((r as Promise<T>).then);
}

export class PollingObserver<T> {
public onfinish?: OnfinishCallback<T>;

private _forceStop = false;
private _records: PollingMeasure[] = [];
private _isPolling = false;

constructor(public conditionCallback: ConditionCallback<T>) {
if ('function' !== typeof(conditionCallback)) {
throw new TypeError(`'conditionCallback' is not defined`);
}
}

public disconnect(): void {
this._forceStop = true;

if (!this._isPolling) this._records = [];
}

public async observe(
callback: PollingCallback<T>,
options?: PollingObserverOptions
): Promise<void> {
/**
* NOTE(motss): To ensure `this._forceStop` is always reset before start observing.
*/
this._forceStop = false;

const { interval, timeout }: PollingObserverOptions = options || {};
const isValidInterval = 'number' === typeof(interval) && interval > 0;
const obsTimeout = 'number' === typeof(timeout) ? +timeout : -1;
const obsInterval = isValidInterval ? +(interval as number) : -1;

const perf = await globalPerformance();
const isInfinitePolling = obsTimeout < 1;
const records = this._records;
const onfinishCallback = this.onfinish;
const conditionCallback = this.conditionCallback;
const loop = true;

let totalTime = 0;
let value: T | null | undefined = void 0;
let i = 0;
let status: OnfinishFulfilled<T>['status'] = 'finish';
let result: OnfinishValue<T> = {} as OnfinishValue<T>;

try {
polling: while (loop) {
if (this._forceStop) break polling;

/** NOTE(motss): Set to indicate polling initiates */
this._isPolling = true;

const conditionResult = conditionCallback(value, records, this);
const didConditionMeet = isPromise(conditionResult) ?
await conditionResult : conditionResult;
const didTimeout = isInfinitePolling ? false : totalTime >= obsTimeout;

if (didTimeout || didConditionMeet) {
status = didTimeout ? 'timeout' : status;
break polling;
}

const startAt = perf.now();
const r = callback();
value = isPromise(r) ? await r : r;
const endAt = perf.now();
const duration = endAt - startAt;
const timeLeft = isValidInterval ? obsInterval - duration : 0;

records.push(new PollingMeasure(`polling:${i}`, duration, startAt));

totalTime += (duration > obsInterval ? duration : obsInterval);
i += 1;

if (timeLeft > 0) await delayUntil(timeLeft);
}

result = { status, value };
} catch (e) {
result = { status: 'error', reason: e };
} finally {
const recordsSlice = records.slice();

if (this._forceStop) this._records = [];

/** NOTE(motss): Reset flags */
this._isPolling = this._forceStop = false;

if ('function' === typeof(onfinishCallback)) {
onfinishCallback(result, recordsSlice, this);
}
}
}

public takeRecords(): PollingMeasure[] {
return this._records;
}
}
export * from './polling-measure.js';
export * from './polling-observer.js';
133 changes: 133 additions & 0 deletions src/polling-observer/polling-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { delayUntil } from '../delay-until/index.js';
import { globalPerformance } from './global-performance.js';
import { PollingMeasure } from './polling-measure.js';

export interface PollingObserverOptions {
timeout?: number;
interval?: number;
}
type PollingCallback<T> = () => T | Promise<T>;
type ConditionCallback<T> = (
data: T | null | undefined,
records: PollingObserver<T>['_records'],
object: PollingObserver<T>
) => boolean | Promise<boolean>;

export interface OnfinishFulfilled<T> {
status: 'finish' | 'timeout';
value: T | null | undefined;
}
export interface OnfinishRejected {
status: 'error';
reason: Error;
}
export type OnfinishValue<T> = OnfinishFulfilled<T> | OnfinishRejected;
type OnfinishCallback<T> = (
value: OnfinishValue<T>,
records: PollingObserver<T>['_records'],
object: PollingObserver<T>
) => unknown;

function isPromise<T>(r: T | Promise<T>): r is Promise<T> {
return 'function' === typeof((r as Promise<T>).then);
}

export class PollingObserver<T> {
public onfinish?: OnfinishCallback<T>;

private _forceStop = false;
private _records: PollingMeasure[] = [];
private _isPolling = false;

constructor(public conditionCallback: ConditionCallback<T>) {
if ('function' !== typeof(conditionCallback)) {
throw new TypeError(`'conditionCallback' is not defined`);
}
}

public disconnect(): void {
this._forceStop = true;

if (!this._isPolling) this._records = [];
}

public async observe(
callback: PollingCallback<T>,
options?: PollingObserverOptions
): Promise<void> {
/**
* NOTE(motss): To ensure `this._forceStop` is always reset before start observing.
*/
this._forceStop = false;

const { interval, timeout }: PollingObserverOptions = options || {};
const isValidInterval = 'number' === typeof(interval) && interval > 0;
const obsTimeout = 'number' === typeof(timeout) ? +timeout : -1;
const obsInterval = isValidInterval ? +(interval as number) : -1;

const perf = await globalPerformance();
const isInfinitePolling = obsTimeout < 1;
const records = this._records;
const onfinishCallback = this.onfinish;
const conditionCallback = this.conditionCallback;
const loop = true;

let totalTime = 0;
let value: T | null | undefined = void 0;
let i = 0;
let status: OnfinishFulfilled<T>['status'] = 'finish';
let result: OnfinishValue<T> = {} as OnfinishValue<T>;

try {
polling: while (loop) {
if (this._forceStop) break polling;

/** NOTE(motss): Set to indicate polling initiates */
this._isPolling = true;

const conditionResult = conditionCallback(value, records, this);
const didConditionMeet = isPromise(conditionResult) ?
await conditionResult : conditionResult;
const didTimeout = isInfinitePolling ? false : totalTime >= obsTimeout;

if (didTimeout || didConditionMeet) {
status = didTimeout ? 'timeout' : status;
break polling;
}

const startAt = perf.now();
const r = callback();
value = isPromise(r) ? await r : r;
const endAt = perf.now();
const duration = endAt - startAt;
const timeLeft = isValidInterval ? obsInterval - duration : 0;

records.push(new PollingMeasure(`polling:${i}`, duration, startAt));

totalTime += (duration > obsInterval ? duration : obsInterval);
i += 1;

if (timeLeft > 0) await delayUntil(timeLeft);
}

result = { status, value };
} catch (e) {
result = { status: 'error', reason: e };
} finally {
const recordsSlice = records.slice();

if (this._forceStop) this._records = [];

/** NOTE(motss): Reset flags */
this._isPolling = this._forceStop = false;

if ('function' === typeof(onfinishCallback)) {
onfinishCallback(result, recordsSlice, this);
}
}
}

public takeRecords(): PollingMeasure[] {
return this._records;
}
}

0 comments on commit 974670b

Please sign in to comment.