@ilyagershman/easy-poll
TypeScript icon, indicating that this package has built-in type declarations

3.0.14 • Public • Published

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

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-invoking init 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 to doPolling, 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 extends PollingContext, meaning it includes all the fields from PollingContext, 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

  1. Immediate Termination: When abort is called, it not only stops the polling but also immediately cancels any ongoing fetch request.
  2. Resource Optimization: Prevents unnecessary network traffic and load on the server by stopping requests that are no longer needed.
  3. 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.

Licence

MIT

Package Sidebar

Install

npm i @ilyagershman/easy-poll

Weekly Downloads

97

Version

3.0.14

License

MIT

Unpacked Size

731 kB

Total Files

61

Last publish

Collaborators

  • ilyagershman