@reallyland/polling-observer
A new way of running polling function with observer pattern
!tippin.metippin-me-badgetippin-me-url !Follow mefollow-me-badgefollow-me-url
!Versionversion-badgeversion-url !Node versionnode-version-badgenode-version-url !MIT Licensemit-license-badgemit-license-url
!Downloadsdownloads-badgedownloads-url !Total downloadstotal-downloads-badgedownloads-url !Packagephobiapackagephobia-badgepackagephobia-url !Bundlephobiabundlephobia-badgebundlephobia-url
!CircleCIcircleci-badgecircleci-url !Dependency Statusdaviddm-badgedaviddm-url !codecovcodecov-badgecodecov-url !Coverage Statuscoveralls-badgecoveralls-url
!codebeat badgecodebeat-badgecodebeat-url !Codacy Badgecodacy-badgecodacy-url !Code of Conductcoc-badgecoc-url
Like PerformanceObserver or any other observer APIs you could find in a browser, but this is for polling. Not only does it run polling with defined parameters but also collect polling metrics for each run until timeout or a defined condition fulfills.
Table of contents
- Node.js - Browser - Install - Usage- [TypeScript or native ES Modules](#typescript-or-native-es-modules)
- [Node.js](#nodejs-1)
- Browser- [ES Modules](#es-modules)
- [UMD](#umd)
- OnfinishFulfilled<T>
- OnfinishRejected
- PollingMeasure- [Methods](#methods)
- [PollingMeasure.toJSON()](#pollingmeasuretojson)
- PollingObserver<T>- [Methods](#methods-1)
- [PollingObserver.observe(callback[, options])](#pollingobserverobservecallback-options)
- [PollingObserver.disconnect()](#pollingobserverdisconnect)
- [PollingObserver.takeRecords()](#pollingobservertakerecords)
- [Event handler](#event-handler)
- [PollingObserver.onfinish](#pollingobserveronfinish)
Pre-requisites
Node.js
- Node.jsnodejs-url >= 8.16.0
- NPMnpm-url >= 6.4.1 (NPMnpm-url comes with Node.jsnodejs-url so there is no need to install separately.)
- perfhooks (Added in
node@8.5.0
behind experimental flag)
Browser
Setup
Install
# Install via NPM
$ npm install --save @reallyland/polling-observer
Usage
Performance API is strictly required before running any polling. To ensureperformance.now
is available globally on Node.js, you can do:/** Node.js */
import { performance } from 'perf_hooks';
global.performance = performance; // or globalThis.performance = performance;
TypeScript or native ES Modules
interface DataType {
status: 'complete' | 'in-progress';
items: Record<string, any>[];
}
import { PollingObserver } from '@reallyland/polling-observer';
const obs = new PollingObserver((data/** list, observer */) => {
const { status, items } = data || {};
const itemsLen = (items && items.length) || 0;
/** Stop polling when any of the conditions fulfills */
return 'complete' === status || itemsLen > 99;
});
obs.observe(
async () => {
/** Polling callback - fetch resources */
const r = await fetch('https://example.com/api?key=123');
const d = await r.json();
return d;
},
/** Run polling (at least) every 2 seconds and timeout if it exceeds 30 seconds */
{
interval: 2e3,
timeout: 30e3,
}
);
/**
* When polling finishes, it will either fulfill or reject depending on the status:
*
* | Status | Returns |
* | ------- | --------- |
* | finish | <value> |
* | timeout | <value> |
* | error | <reason> |
*
*/
obs.onfinish = (data, records/**, observer */) => {
const { status, value, reason } = data || {};
switch (status) {
case 'error': {
console.error(`Polling fails due to: `, reason);
break;
}
case 'timeout': {
console.log(`Polling timeouts after 30 seconds: `, value);
break;
}
case 'finish':
default: {
console.log(`Polling finishes: `, value);
}
}
console.log(`Formatted polling records: `, records.map(n => n.toJSON()));
/**
* [
* {
* duration: 100,
* entryType: 'polling-measure',
* name: 'polling:0',
* startTime: 100,
* },
* ...
* ]
*/
obs.disconnect(); /** Disconnect to clean up */
};
Node.js
const { PollingObserver } = require('@reallyland/polling-observer');
const obs = new PollingObserver((data/** entries, observer */) => {
const { status, items } = data || {};
const itemsLen = (items && items.length) || 0;
/** Stop polling when any of the conditions fulfills */
return 'complete' === status || itemsLen > 99;
});
obs.observe(
async () => {
/** Polling callback - fetch resources */
const r = await fetch('https://example.com/api?key=123');
const d = await r.json();
return d;
},
/** Run polling (at least) every 2 seconds and timeout if it exceeds 30 seconds */
{
interval: 2e3,
timeout: 30e3,
}
);
/**
* When polling finishes, it will either fulfill or reject depending on the status:
*
* | Status | Returns |
* | ------- | --------- |
* | finish | <value> |
* | timeout | <value> |
* | error | <reason> |
*
*/
obs.onfinish = (data, entries/**, observer */) => {
const { status, value, reason } = data || {};
switch (status) {
case 'error': {
console.error(`Polling fails due to: `, reason);
break;
}
case 'timeout': {
console.log(`Polling timeouts after 30 seconds: `, value);
break;
}
case 'finish':
default: {
console.log(`Polling finishes: `, value);
}
}
console.log(`Formatted polling entries: `, entries.map(n => n.toJSON()));
/**
* [
* {
* duration: 100,
* entryType: 'polling-measure',
* name: 'polling:0',
* startTime: 100,
* },
* ...
* ]
*/
obs.disconnect(); /** Disconnect to clean up */
};
Browser
ES Modules
<!doctype html>
<html>
<head>
<script type="module">
import { PollingObserver } from 'https://unpkg.com/@reallyland/polling-observer@latest/dist/polling-observer.min.js';
// --snip
</script>
</head>
</html>
UMD
<!doctype html>
<html>
<head>
<script src="https://unpkg.com/@reallyland/polling-observer@latest/dist/polling-observer.umd.min.js"></script>
<script>
var { PollingObserver } = window.PollingObserver;
// --snip
</script>
</head>
</html>
Demo
API Reference
OnfinishFulfilled<T>
interface OnfinishFulfilled<T> {
status: 'finish' | 'timeout';
value: T;
}
OnfinishRejected
interface OnfinishRejected {
status: 'error';
reason: Error;
}
PollingMeasure
interface PollingMeasure {
duration: number;
entryType: 'polling-measure';
name: string;
startTime: number;
}
duration
<numbernumber-mdn-url> Duration of the polling takes in milliseconds.entryType
<stringstring-mdn-url> Entry type, defaults topolling-measure
.name
<stringstring-mdn-url> Polling name in the format ofpolling:<index>
where<index>
starts from0
and increments on each polling.startTime
<stringstring-mdn-url> Relative timestamp (in milliseconds ) indicates when the polling starts at.
Methods
PollingMeasure.toJSON()
- <Functionfunction-mdn-url> Returns a JSON representation of the polling object's properties.
PollingObserver<T>
conditionCallback
<Functionfunction-mdn-url> Condition callback to be executed in each polling and return the condition result in the type of boolean, e.g. returntrue
to stop next poll.
data
<T
> Polling data returned by callback
in the type of T
which defined in the PollingObserver.observe() method.
- entries
<Arrayarray-mdn-url<PollingMeasure>> A list of PollingMeasure objects.
- observer
<PollingObserver<T
>> Created PollingObserver object.
- returns: <booleanboolean-mdn-url> If true
, the polling stops. Returning false
will result in an infinite polling as the condition will never meet.- returns: <PollingObserver<
T
>> PollingObserver object.
Methods
PollingObserver.observe(callback, options)
The method is used to initiate polling with a polling callback and optional configuration.callback
<Functionfunction-mdn-url> Callback to be executed in each polling and return the result so that it will be passed as the first argument inconditionCallback
.
T
| Promisepromise-mdn-url<T
>> Return polling result in the type of T
or Promise<T>
in each polling.options
<Objectobject-mdn-url> Optional configuration to run the polling.
interval
<numbernumber-mdn-url> Optional interval in milliseconds. This is the minimum delay before starting the next polling.
- timeout
<numbernumber-mdn-url> Optional timeout in milliseconds. Polling ends when it reaches the defined timeout even though the condition has not been met yet. As long as timeout
is not a number or it has a value that is less than 1, it indicates an infinite polling. The polling needs to be stopped manually by calling PollingObserver.disconnect() method.PollingObserver.disconnect()
Once aPollingObserver
disconnects, the polling stops and all polling metrics will be cleared. Calling PollingObserver.takeRecords() after the disconnection will always return an empty record.A
onfinish
event handler can be used to retrieve polling records after a disconnection but it has to be attached before disconnecting the observer.PollingObserver.takeRecords()
The method returns a list of PollingMeasure object containing the metrics of each polling.- returns: <Arrayarray-mdn-url<PollingMeasure>> A list of PollingMeasure objects.
Event handler
PollingObserver.onfinish
Note that nofinish
event fires when the polling finishes. So observer.addEventListener('finish', ...)
will not work.Event handler for when a polling finishes. When a polling finishes, it can either be fulfilled with a
value
or rejected with a reason
. Any one of which contains a status
field to tell the state of the finished polling.When a polling fulfills, it returns an OnfinishFulfilled<T> object with
status
set to finish
or timeout
and a value
in the type of T
.When a polling rejects, it returns an OnfinishRejected object with
status
set to error
and a reason
in the type of Errorerror-mdn-url.| Status | Returns | | --------- | -------------- | |
finish
| <value> |
| timeout
| <value> |
| error
| <reason> |