@ilyagershman/easy-poll
v3.0.14
Published
⭐️ Easy to use, fully typed and well tested web/nodejs library for polling. Examples with sandboxes included! ⭐️
Downloads
596
Maintainers
Readme
Welcome to easy-poll
🌟 Dive into the world of effortless, fully-typed, and rigorously tested polling with easy-poll! Including hands-on examples and interactive sandboxes to kickstart your development. 🌟
Contents
- Welcome to easy-poll
- Quick Start with Examples
- Installation Guide
- API Overview
- Understanding
doPolling()
- Understanding
subscribePolling()
- How to Abort Polling
- Support and Feedback
- Real-World Examples
- Licence
Quick Start with Examples
Jump straight into action with our live examples:
For React Users: Explore our React integration with this interactive sandbox.
For Node.js Users: Check out how easy-poll works in a Node.js environment with this node example sandbox. (Note: NodeJS 16+ required)
More Examples: Visit the examples directory on our GitHub repository for a variety of use cases. Each example includes a README for guidance on running it locally.
Installation Guide
Get started with easy-poll in your project by running one of the following commands:
For npm users:
npm install --save @ilyagershman/easy-poll
For yarn users:
yarn add @ilyagershman/easy-poll
For pnpm users:
pnpm install --save @ilyagershman/easy-poll
Choose the command compatible with your package manager to add easy-poll to your project dependencies.
API Overview
The easy-poll
library offers two primary functions to cater to your polling needs: doPolling
and subscribePolling
.
doPolling
This function initiates a polling process, returning an object that includes init
and abort
methods:
init
: Starts the polling sequence and returns a promise with the polling result. Re-invokinginit
will return the initial promise instead of creating a new polling instance.abort
: Stops the polling process and returns the promise with the result of the last poll, regardless of success or failure.
subscribePolling
This function is designed for scenarios where you want to monitor polling events externally, returning an object with subscribe
, init
, and abort
methods:
subscribe
: Registers a callback to be invoked on each polling event, receiving data about the polling status.init
: Similar todoPolling
, it begins the polling and returns a promise with the outcome.abort
: Ends the polling and provides the promise with the last known state of the polling process.
Both functions allow for detailed configuration to fine-tune the polling behavior, including retry limits, error handling, polling intervals, and custom event callbacks.
Understanding doPolling()
Import the doPolling
function from the library and pass it the function that you want to poll. Pass the options object as a second parameter. Here is an example of how to use it:
import { doPolling } from '@ilyagershman/easy-poll';
const { init, abort } = await doPolling(fetchStuff,
{
// max retries count. If maxPolls is reached, onTooManyAttempts will be called
maxPolls: 10, // default is Infinity
// max errors count. If maxErrors is reached, onTooManyErrors will be called
maxErrors: 5, // default is 5
// interval between retries. Can be a number or function that is called on every poll
interval: 1000, // default is 2000
// polling will be stopped if condition is true. If condition is not provided, polling will be stopped after one successful request
until: ({ data }) => data.status === 'SUCCESS', // default is () => true
// onStart will be called before polling
onStart: () => {},
// onNext will be called after each successful poll, except the last one
onNext: ({ data, attempt, errorsCount }) => {},
// onComplete will be called after polling is completed
onComplete: ({ data, attempt, errorsCount }) => {},
// onFinish will be called after polling is finished with whichever result
onFinish: ({ data, attempt, errorsCount }) => {},
// onError will be called after each failed poll
onError: ({ retry, errorsCount, error }) => {},
// onTooManyAttempts will be called if maxPolls is reached.
onTooManyAttempts: () => {},
// onTooManyErrors will be called if maxErrors is reached.
onTooManyErrors: ({ retry, errorsCount, error }) => {},
// onIntervalError will be called if the interval function throws an error
onIntervalError({ data, error, attempt, attemptsDuration, errorsCount, duration }) => {},
// polling will be stopped if breakIf is true. This is useful when you want to stop polling if you know that you will never get the result you want.
breakIf: ({ data }) => data.received !== total,
// onBreak will be called if breakIf is true
onBreak: ({ data, attempt, errorsCount }) => {},
// breakIfError acts the same as breakIf, but for errors. It is useful when you want to stop polling if you receive a specific error type.
breakIfError: ({ error }) => error.code === 404,
// onBreakError will be called if breakIfError is true
onBreakError: ({ error, attempt, errorsCount }) => {},
}
);
// now simply call init to start polling
init().then(({ error, data }) => {
// data is the result of the last poll (if it was successful)
// error is the error of the last poll (if it was failed)
});
// you can also abort the polling from the outside
addEventListener('click', () => abort());
Basic Usage
As you can see, you have a lot of control over the polling process. But all the options props are optional, you can simply use it like this:
doPolling(fetchStuff).init().then(...); // this will act like a simple fetch with 5 retries
or provide only the until
condition:
const { data, error } = await doPolling(fetchStuff, { until: ({ data }) => data === 'the needed result' }).init();
Options API
Here's a detailed description of each property in the doPolling
function's options object:
maxPolls
- Type:
number
- Default:
Infinity
- Description: The maximum number of times to attempt polling. When this count is reached,
onTooManyAttempts
is triggered.
maxErrors
- Type:
number
- Default:
5
- Description: The maximum number of errors allowed before stopping the polling.
onTooManyErrors
is called when this limit is reached.
interval
- Type:
number | ((context: PollingContext) => number)
- Default:
2000
- Description: The time interval (in milliseconds) between polling attempts. It can be a constant value or a function that returns a value, allowing dynamic adjustment based on previous attempts or conditions. With this you can create your own strategy for the use-case
doPolling(fetchStuff, {
until: ({ data }) => data === 'the needed result',
interval: ({ data, attempt }) => attempt * 1000, // now every attempt is going to be less and less frequent
});
or
interval: () => getRandomNumber(100, 6000), // super sneaky strategy - not even you will know when the next attempt is going to be. How do you like that, Elon Musk?
or even
interval: ({ error }) => (!!error ? 42000 : 4200), // let's introduce some cooldown period after an error
until
- Type:
((context: PollingContext) => boolean)
- Default:
() => true
- Description: A function that evaluates whether the polling should stop based on the fetched data. If the function returns
true
, polling stops.
onStart
- Type:
() => void
- Description: A callback function that is executed before the polling starts.
onNext
- Type:
((context: PollingSuccessContext) => void)
- Description: A callback function called after each successful polling attempt, except for the last one.
onComplete
- Type:
((context: PollingSuccessContext) => void)
- Description: A callback function executed when the polling successfully completes.
onFinish
- Type:
((context: PollingContext) => void)
- Description: A callback function called after the polling process finishes, regardless of the outcome.
onError
- Type:
((context: PollingErrorContext) => void)
- Description: A callback function triggered after each failed polling attempt.
onTooManyAttempts
- Type:
(context: PollingSuccessContext) => void
- Description: A callback function called when the number of polling attempts exceeds
maxPolls
.
onTooManyErrors
- Type:
((context: PollingErrorContext) => void)
- Description: A callback function triggered when the number of errors reaches
maxErrors
.
onIntervalError
- Type:
((context: PollingContext) => void)
- Description: A callback function called if an error occurs within the interval function.
breakIf
- Type:
((context: PollingContext) => boolean)
- Description: A condition that, if true, stops the polling process immediately. Useful for ending polling based on certain data conditions.
onBreak
- Type:
((context: PollingSuccessContext) => void)
- Description: A callback function executed when polling is stopped by
breakIf
.
breakIfError
- Type:
((context: PollingErrorContext) => boolean)
- Description: Similar to
breakIf
, but specifically for halting polling due to certain types of errors.
onBreakError
- Type:
((context: PollingErrorContext) => void)
- Description: A callback function called when
breakIfError
condition is met.
These properties provide a granular level of control over the polling process, allowing developers to precisely manage how polling behaves in different scenarios, handle errors effectively, and implement custom logic at various stages of the polling lifecycle.
In the context of the doPolling
function, PollingErrorContext
and PollingContext
provide specific details about the polling state and error conditions. Here’s how these contexts are structured and what information they provide:
Polling Contexts
These contexts are passed to various callback functions to provide information about the current state of the polling process.
These contexts are designed to give detailed insights into the polling process, allowing developers to implement sophisticated logic in their callbacks, handling success and failure cases effectively, and making informed decisions based on the current state of the polling operation.
PollingContext
This context is used to provide general information about the polling process.
type PollingContext<T> = {
data: T;
attempt: number;
attemptsDuration: number[];
errorsCount: number;
duration: number;
error: any;
};
data
: Contains the result of the latest successful polling request.attempt
: Indicates which polling attempt is currently being processed.errorsCount
: Counts how many errors have occurred during the polling process.attemptsDuration
: Tracks the time spent in each polling attempt.duration
: Represents the total time from the start of the polling process, including the time spent waiting between attempts.error
: Provides the error that occurred during the latest polling attempt. This extendsPollingContext
, meaning it includes all the fields fromPollingContext
, along with the error information.
PollingSuccessContext
This context is specifically used in success-related callbacks to provide details about the successful polling attempt.
type PollingSuccessContext<T> = {
data: T;
attempt: number;
attemptsDuration: number[];
errorsCount: number;
duration: number;
};
PollingErrorContext
This context is specifically used in error-related callbacks to provide details about the error condition within the polling process.
type PollingSuccessContext<T> = {
error: any;
attempt: number;
attemptsDuration: number[];
errorsCount: number;
duration: number;
};
Understanding subscribePolling()
subscribePolling is designed for situations where you need to respond to events during the polling process. It allows you to subscribe to polling events and handle them accordingly.
When called, the function returns an object with two functions: subscribe
, init
and abort
.
init
is a function that starts the polling. It returns a promise with the result of the polling (same as doPolling
).
Every time polling event is triggered, the callback passed to the subscribe
function will be called. The callback function will receive an object with the following properties:
{
event: EVENTS; // the name of the event. Simply import { EVENTS } from '@ilyagershman/easy-poll'
props: PollingContext | PollingErrorContext;
}
Consider the following example:
// someService.ts
import { subscribePolling } from '@ilyagershman/easy-poll';
export const { subscribe, init, abort } = subscribePolling(fetchStuff, {
// max retries count. If maxPolls is reached, onTooManyAttempts will be called
maxPolls: 10,
// max errors count. If maxErrors is reached, onTooManyErrors will be called
maxErrors: 5,
// interval between retries. Can be a number or function that is called on every poll
interval: () => getRandomInt(10000),
// polling will be stopped if the condition is true. If the condition is not provided, polling will be stopped after one successful request
until: ({ data }) => data.status === 'SUCCESS',
// polling will be stopped if breakIf is true. This is useful when you want to stop polling if you know that you will never get the result you want.
breakIf: ({ data }) => data.received !== total,
// breakIfError acts the same as breakIf, but for errors. It is useful when you want to stop polling if you receive a specific error type.
breakIfError: ({ error }) => error.code === 404,
});
/// somewhere.ts in your code react on the polling events.
import { subscribe, init } from './somewhere';
import { EVENTS } from '@ilyagershman/easy-poll';
init().then({ data, error } => {
// same as doPolling
}));
subscribe(props => {
if (props.event === EVENTS.ON_COMPLETE) onComplete(props);
if (props.event === EVENTS.ON_BREAK) onBreak(props);
if (props.event === EVENTS.ON_NEXT) onNext(props);
if (props.event === EVENTS.ON_ERROR) onError(props);
if (props.event === EVENTS.ON_FINISH) onFinish(props);
if (props.event === EVENTS.ON_TOOMANYERRORS) onTooManyErrors(props);
if (props.event === EVENTS.ON_TOOMANYATTEMPTS) onTooManyAttempts(props);
// ...
});
/// somewhereElse.ts in your code react on the polling events.
import { abort } from './somewhere';
// this will abort the polling after some time
setTimeout(abort, 4200);
How to Abort Polling
To terminate an ongoing polling process in easy-poll
, you can use the abort
method provided by both doPolling
and subscribePolling
. This method stops the polling immediately and returns the state at the time of abortion.
Using abort
in doPolling
When you initiate polling with doPolling
, it returns an object containing the abort
function among others. Here’s how you can use it:
import { doPolling } from '@ilyagershman/easy-poll';
const { init, abort } = await doPolling(yourFunctionToPoll);
// Start polling
init();
// To abort polling, simply call the abort method
abort();
Using abort
in subscribePolling
Similarly, when using subscribePolling
, you also get an abort
function that you can call to stop the polling:
import { subscribePolling } from '@ilyagershman/easy-poll';
const { init, abort } = subscribePolling(yourFunctionToPoll);
// Start polling
init();
// To abort polling, call the abort method
abort();
Handling Abort in Your Application
You can tie the abort
function to user actions or application events. For example, you might want to abort the polling when the user navigates away from a page or when a certain condition is met:
addEventListener('unload', abort); // Abort polling when the user leaves the page
Or, in a React component, you might use it in a useEffect
cleanup function:
useEffect(() => {
init(); // Start polling on component mount
return () => {
abort(); // Abort polling on component unmount
};
}, []);
Using the abort
method provides you with the flexibility to manage the polling process according to your application’s needs, ensuring that resources are not wasted and that the application behaves predictably.
To enhance the control over network requests during polling, easy-poll
allows you to pass an abort
signal to the fetch
function. This enables you to cancel ongoing network requests if the polling is aborted.
Passing Abort Signal to Fetch
When you initialize the polling, you can pass a function that includes the signal
parameter. This signal
should then be passed to the fetch
function to allow request cancellation:
import { doPolling } from '@ilyagershman/easy-poll';
const { init, abort } = await doPolling(({ signal }) => fetch('https://api.example.com/data', { signal }));
// Start polling
init();
// The fetch request can now be aborted by calling the abort function
addEventListener('click', abort); // For example, abort on a button click
In this example, signal
is an instance of AbortSignal
, which is part of the AbortController
Web API. By passing this signal
to the fetch
call, you link the abort control of the polling process directly to the network request.
Benefits of Using Abort Signal
- Immediate Termination: When
abort
is called, it not only stops the polling but also immediately cancels any ongoingfetch
request. - Resource Optimization: Prevents unnecessary network traffic and load on the server by stopping requests that are no longer needed.
- Better Error Handling: Facilitates cleaner and more manageable error handling by distinguishing between user-aborted requests and other types of network errors.
This integration ensures that your polling logic is efficient, responsive, and respectful of both client and server resources.
You can learn more about AbortControllers and Signals here and here.
Support and Feedback
Do you have questions or need assistance with easy-poll
? Your feedback and inquiries are welcome!
Opening an Issue
If you encounter any problems or have suggestions for improving easy-poll
, please feel free to open an issue on our GitHub repository:
Get Involved
Your contributions make easy-poll
better for everyone. Whether it's reporting bugs, discussing new features, or improving documentation, we value your input and encourage your participation.
Stay Connected
Join our community and stay up-to-date with the latest developments. Your insights and expertise can help shape the future of easy-poll
!
By engaging with the community, you can help improve the tool and find support for any challenges you encounter.
Spread the Word
Help us grow by sharing easy-poll
within your network:
- Star the Repo: Click the star button on GitHub to increase visibility.
- Share on Social Media: Let others know about
easy-poll
on Twitter, LinkedIn, or your favorite tech community. - Recommend to Peers: If
easy-poll
has made your development process easier, recommend it to colleagues and friends.
Why Your Support Matters
Your endorsement not only motivates us to improve and expand easy-poll
but also helps build a community around it. A larger community means more ideas, feedback, and contributions, leading to a robust and feature-rich library.
Thank you for considering to support and share easy-poll
. Together, we can make it an indispensable tool for developers everywhere!
Real-World Examples
Let’s explore some real-world scenarios where easy-poll
can be used to enhance the functionality of your applications. These examples demonstrate how polling can be applied to various use cases, such as e-commerce inventory checks, social media feed updates, system monitoring, and more.
E-commerce Inventory Check
An e-commerce application needs to regularly check the availability of a high-demand product. To prevent overwhelming the server, the app uses doPolling
to check inventory status every 30 seconds, increasing the interval after each attempt until a successful response is received or a maximum of 10 attempts is reached.
import { doPolling } from '@ilyagershman/easy-poll';
async function checkProductAvailability(productId) {
const response = await fetch(`https://api.store.com/products/${productId}/availability`);
return response.json();
}
const { init, abort } = doPolling(() => checkProductAvailability('12345'), {
maxPolls: 10,
interval: attempt => 30000 + attempt * 5000, // Increase interval by 5 seconds after each attempt
until: ({ data }) => data.isAvailable,
});
init();
Social Media Feed Update
A social media application wants to update the user's feed only when new content is available. Using doPolling
, it can efficiently poll the server for updates without constant querying, reducing unnecessary network usage.
import { doPolling } from '@ilyagershman/easy-poll';
function fetchNewSocialMediaPosts(lastUpdate) {
return fetch(`https://api.socialmedia.com/posts?since=${lastUpdate}`).then(res => res.json());
}
const { init } = doPolling(() => fetchNewSocialMediaPosts(lastFetchTimestamp), {
interval: 60000, // Check every minute
until: ({ data }) => data.length > 0,
});
init().then(({ data }) => {
console.log('New posts:', data);
lastFetchTimestamp = new Date().toISOString(); // Update last fetch timestamp
});
Monitoring System Status
For a system monitoring tool, it’s crucial to continuously check the health of various services. doPolling
can be used to implement this functionality, with the ability to break the polling process if a critical error is detected.
import { doPolling } from '@ilyagershman/easy-poll';
async function checkSystemHealth(signal) {
const response = await fetch('https://api.system.com/health', { signal });
return response.json();
}
const { init, abort } = doPolling(({ signal }) => checkSystemHealth(signal), {
interval: 5000, // Poll every 5 seconds
breakIfError: ({ error }) => error.statusCode === 500, // Stop polling on critical errors
onNext: ({ data }) => {
if (data.status === 'OK') {
console.log('System is healthy!');
return;
}
console.error('System is down:', data.error);
},
});
init();
addEventListener('unload', abort); // Abort polling when the user leaves the page
Real-Time Stock Market Updates
A financial application tracks real-time changes in stock prices. To provide timely updates without overloading the server, doPolling
can be set to poll the server at a higher frequency during market hours and less frequently after hours.
/// reportAggregationService.ts
import { doPolling } from '@ilyagershman/easy-poll';
function fetchStockPrice(stockSymbol, signal) {
return fetch(`https://api.finance.com/stocks/${stockSymbol}`, { signal }).then(res => res.json());
}
function sendReportToServer(report) {
fetch('https://api.finance.com/reports', {
method: 'POST',
body: JSON.stringify(report),
});
}
export const startReportAggregation = () => {
const report = {
AAPL: [{ price: 0, time: 0 }],
};
const { init, abort } = doPolling(({ signal }) => fetchStockPrice('AAPL', signal), {
interval: ({ attempt, errorsCount }) => {
const isMarketHours = new Date().getHours() >= 9 && new Date().getHours() < 16;
return isMarketHours ? 1000 : 60000; // 1 second during market hours, 1 minute otherwise
},
onNext: ({ data, duration }) => {
console.log(`Latest stock price of AAPL: $${data.price}`);
report.AAPL.push({ price: data.price, time: duration });
},
});
init().then(() => {
console.log('Stock market closed. Final report:', report);
sendReportToServer(report);
});
setTimeout(() => abort(), 60 * 24 * 60); // Stop polling after 24 hours
};
/// main.ts
import { startReportAggregation } from './reportAggregationService';
Tracking Parcel Delivery Status
For a logistics application, customers want to track their parcel delivery status in real-time. doPolling
can help by polling the delivery service's API until the parcel status changes to "Delivered".
import { doPolling } from '@ilyagershman/easy-poll';
function checkParcelStatus(parcelId) {
return fetch(`https://api.logistics.com/parcels/${parcelId}/status`).then(res => res.json());
}
const { init } = doPolling(() => checkParcelStatus('123456789'), {
until: ({ data }) => data.status === 'Delivered',
interval: 30000, // Check every 30 seconds
});
init().then(() => {
console.log('Parcel has been delivered!');
});
Automated Help Desk Ticket Updates
In a customer service application, doPolling
can be used to automatically update the status of help desk tickets, ensuring that service agents and customers have the most current information without manual refreshing.
import { doPolling } from '@ilyagershman/easy-poll';
function fetchTicketStatus(ticketId) {
return fetch(`https://api.helpdesk.com/tickets/${ticketId}/status`).then(res => res.json());
}
const { init, abort } = doPolling(() => fetchTicketStatus('ticket-1234'), {
interval: 10000, // Poll every 10 seconds
until: ({ data }) => data.status === 'Resolved',
onComplete: ({ duration, attempts }) => {
console.log(
`Ticket was resolved after ${Number(duration / 1000 / 60).toFixed(1)} minutes with ${attempts} attmepts`
);
},
});
These examples demonstrate how doPolling
can be adapted to various real-world scenarios, providing a robust solution for different polling requirements.
Real-Time Chat Application
In a chat application, subscribePolling
can be used to fetch new messages periodically, updating the chat interface in real time as new messages arrive.
/// messageService.ts
import { subscribePolling, EVENTS } from '@ilyagershman/easy-poll';
function fetchNewMessages(chatId) {
return fetch(`https://api.chatapp.com/chats/${chatId}/messages`).then(res => res.json());
}
export const { subscribe, init, abort } = subscribePolling(() => fetchNewMessages('chat123'), {
interval: ({ error }) => (error ? 42000 : 4200), // Increase interval after an error
breakIfError: ({ error }) => error.code === 404, // Stop polling on 404 errors
onBreakError: ({ error }) => console.error('Chat not found:', error),
});
/// useMessages.ts
import { init as collectMessages, abort } from './messageService';
useEffect(() => {
return () => {
abort(); // Stop polling when the component unmounts
};
}, []);
return { collectMessages: init };
/// chatUI.ts
subscribe(({ event, props }) => {
if (event === EVENTS.ON_NEXT) {
updateChatUI(props.data); // Update the UI with new messages
}
});
/// chatStats.ts
subscribe(({ event, props }) => {
if (event === EVENTS.ON_NEXT) {
updateTotalMessagesUI(props.data); // Update the UI with new messages
}
});
/// chatNotifications.ts
subscribe(({ event, props }) => {
if (event === EVENTS.ON_NEXT) {
updateUnreadMessagesUI(props.data); // Update the UI with new messages
}
});
Live Sports Score Updates
For a sports app, subscribePolling
can be utilized to continuously check for and display updated scores of ongoing games, providing fans with real-time score updates.
import { subscribePolling, EVENTS } from '@ilyagershman/easy-poll';
function fetchCurrentGameScore(gameId) {
return fetch(`https://api.sports.com/games/${gameId}/score`).then(res => res.json());
}
const { subscribe, init } = subscribePolling(() => fetchCurrentGameScore('game123'), {
interval: 30000, // Update every 30 seconds
});
init();
subscribe(({ event, props }) => {
if (event === EVENTS.ON_NEXT) {
displayScore(props.data); // Display the latest score on the screen
}
});
Monitoring Server Performance
In an IT infrastructure management tool, subscribePolling
can be employed to monitor server performance metrics and trigger alerts or actions based on specific thresholds.
import { subscribePolling, EVENTS } from '@ilyagershman/easy-poll';
function getServerPerformanceMetrics(serverId) {
return fetch(`https://api.itmanagement.com/servers/${serverId}/performance`).then(res => res.json());
}
const { subscribe, init } = subscribePolling(() => getServerPerformanceMetrics('server1'), {
interval: 10000, // Poll every 10 seconds
});
init();
subscribe(({ event, props }) => {
if (event === EVENTS.ON_NEXT) {
if (props.data.cpuUsage > 90) {
alert('High CPU usage detected!');
}
}
});
These examples demonstrate how subscribePolling
facilitates real-time data updates and interactive user experiences in various applications, ranging from messaging and live events to system monitoring, by efficiently managing continuous data fetching and event handling.