Giter VIP home page Giter VIP logo

promise-poller's Introduction

promise-poller

A basic poller built on top of promises.

Sometimes, you may perform asynchronous operations that may fail. In many of those cases, you want to retry these operations one or more times before giving up. promise-poller handles this elegantly using promises.

Usage

Basic usage

The core of promise-poller is a task function. This is simply a function that starts your asynchronous task and returns a promise. If the task function does not return a promise, it will be wrapped in a promise. To start polling, pass your task function to the promisePoller function:

import promisePoller from 'promise-poller';

function myTask() {
  // do some async stuff that returns a promise
  return promise;
}

var poller = promisePoller({
  taskFn: myTask
});

The promisePoller function will return a "master promise". This promise will be resolved when your task succeeds, or rejected if your task fails and no retries remain.

The master promise will be resolved with the value that your task promise is resolved with. If the poll fails, the master promise will be rejected with an array of all the rejection reasons for each poll attempt.

promise-poller will attempt your task by calling the function and waiting on the returned promise. If the promise is rejected, promise-poller will wait one second and try again. It will attempt to execute your task 3 times before rejecting the master promise.

Use in non-ES2015 environments

promise-poller is written using ES2015 and transpiled with Babel. The main promisePoller function is the default export. If you are using promise-poller in an ES5 environment, you will have to specify the default property when requiring the library in:

var promisePoller = require('promise-poller').default;

Specify polling options

You can specify a different polling interval or number of retries:

var poller = promisePoller({
  taskFn: myTask,
  interval: 500, // milliseconds
  retries: 5
});

Specify timeout

If you want each poll attempt to reject after a certain timeout has passed, use the timeout option:

var poller = promisePoller({
  taskFn: myTask,
  interval: 500,
  timeout: 2000
});

In the above example, the poll is considered failed if it isn't resolved after 2 seconds. If there are retries remaining, it will retry the poll as usual.

Specify "master timeout"

Instead of timing out each poll attempt, you can set a timeout for the entire master polling operation:

var poller = promisePoller({
  taskFn: myTask,
  interval: 500,
  retries: 10,
  masterTimeout: 2000
});

In the above example, the entire poll operation will fail if there is not a successful poll within 2 seconds. This will reject the master promise.

Cancel polling

You may want to cancel the polling early. For example, if the poll fails because of an invalid password, that's not likely to change, so it would be a waste of time to continue to poll. To cancel polling early, return false from the task function instead of a promise.

Alternatively, if your task function involves async work with promises, you can reject the promise with the CANCEL_TOKEN object.

Cancellation example

import promisePoller, { CANCEL_TOKEN } from 'promise-poller';

const taskFn = () => {
  return new Promise((resolve, reject) => {
    doAsyncStuff().then(resolve, error => {
      if (error === 'invalid password') {
        reject(CANCEL_TOKEN); // will cancel polling
      } else {
        reject(error); // will continue polling
      }
    });
  });
}

The shouldContinue function

You can specify an optional shouldContinue function that takes two arguments. The first argument is a rejection reason when a poll fails, and the second argument is the resolved value when a poll succeeds. If the poll attempt failed, and you want to abort further polling, return false from this function. On the other hand, if your poll resolved to a value but you want to keep polling, return true from this function.

Select polling strategy

By default, promise-poller will use a fixed interval between each poll attempt. For example, with an interval option of 500, the poller will poll approximately every 500 milliseconds. This is the fixed-interval strategy. There are two other strategies available that may better suit your use case. To select a polling strategy, specify the strategy option, e.g.:

promisePoller({
  taskFn: myTask,
  strategy: 'linear-backoff'
});

Linear backoff (linear-backoff)

Options:

  • start - The starting value to use for the polling interval (default = 1000)
  • increment - The amount to increase the interval by on each poll attempt.

Linear backoff will increase the interval linearly by some constant amount for each poll attempt. For example, using the default options, the first retry will wait 1000 milliseconds. Each successive retry will wait an additional 1000 milliseconds: 1000, 2000, 3000, 4000, etc.

Exponential backoff with jitter (exponential-backoff)

Options:

  • min - The minimum interval amount to use (default = 1000)
  • max - The maximum interval amount to use (default = 30000)

Exponential backoff increases the poll interval by a power of two for each poll attempt. promise-poller uses exponential backoff with jitter. Jitter takes a random value between min and 2^n on the nth polling interval, not to exceed max.

For more information about exponential backoff with jitter, and its advantages, see https://www.awsarchitectureblog.com/2015/03/backoff.html.

Progress notification

You can also specify a progress callback function. Each time the task fails, the progress callback will be called with the number of retries remaining and the error that occurred (the value that the task promise was rejected with):

function progress(retriesRemaining, error) {
  // log the error?
}

var poller = promisePoller({
  taskFn: myTask,
  interval: 500,
  retries: 5,
  progressCallback: progress
});

Debugging

promise-poller uses the debug library. The debug name is promisePoller. To run your program with debug output for the promise-poller, set the DEBUG environment variable accordingly:

% DEBUG=promisePoller node path/to/app.js

If you have more than one poller active at a time, and you need to differentiate between them in debug output, you can give the promisePoller options a name property:

var poller = promisePoller({
  taskFn: myTask,
  interval: 500,
  retries: 5,
  name: 'App Server Poller'
});

When this poller prints debug messages, the poller name will be included:

promisePoller (App Server Poller) Poll failed. 1 retries remaining. +504ms

Contributors

  • Joe Attardi
  • /u/jcready
  • Jason Stitt
  • Emily Marigold Klassen

License

The MIT License (MIT)

Copyright (c) 2016-2019 Joe Attardi [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

promise-poller's People

Contributors

forivall avatar jasonstitt avatar joeattardi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

promise-poller's Issues

Passing arguments to taskFn

Apologies if I missed it in the documentation, but is it possible to pass arguments to the taskFn? Thanks for your work on this project!

Timeout does not work

The timeout-functionality seems to be broken:

require('promise-poller')({
  taskFn: () => {
    console.log('polling...');
    return Promise.reject();
  },
  interval: 10,
  timeout: 1000,
})
.then(() => console.log('success'))
.catch(() => console.log('failed'));

This will log "polling..." 5 times (which seems to be the default) and then fails. When adding retries: Infinity, it keeps polling infinitely.

Any idea what's up?

Add "total poll" timeout

Currently, the timeout option sets the timeout for each individual poll. It might also be handy to have a timeout for the entire poll operation.

So, for example:

timeout: 10000 means "every time you poll, reject the promise for that poll iteration if it's not resolved after 10 seconds.

totalTimeout: 10000 means "while polling one or more times, reject the master promise if there isn't a successful poll after 10 seconds."

Add timeout support

There may be use cases where a poll should fail after a given timeout if the promise isn't settled by then.

Interval option not working

When I pass 60000 as the interval, it calls my function every 10 seconds (estimate) instead of every 60 seconds.

  promisePoller({
        taskFn: pollTask,
        interval: 60000, // milliseconds
        retries: 3,
        shouldContinue: () => shouldContinue,
      });

Cannot cancel from within promise

You may want to cancel the polling early. For example, if the poll fails because of an invalid password, that's not likely to change, so it would be a waste of time to continue to poll. To cancel polling early, return false from the task function instead of a promise.

The use cases I have had so far for polling all involve async tasks, which cannot cancel the polling operation if they encounter an error that should be fatal, because they can't make the task function return false instead of a promise.

One possibility would be to add a unique sentinel value/error type for cancelation.

A possible workaround is to maintain a flag in the enclosing scope that allows cancellation on the next invocation, but this isn't as nice as canceling immediately especially if the interval is high.

Errors ignored when setting timeout

When setting the timeout property any error caused by the poll task will be ignored.
The poller will wait for the timeout to run out and swallow the actual error message.

Is being addressed in #25

@joeattardi could you please approve the PR to be merged? Thanks ๐Ÿ™

masterTimeout value is not taken into account (globalTimeout is used)

I've try a stupid test by setting the masterTimeout value and it seems not taken into account, there is a kind of timeout that occur but not at the time specified in masterTimeout value
I've look at the code, when masterTimeout then it create a setTiemout with the field options.globalTimeout:

if (options.**masterTimeout**) { setTimeout(() => reject('master timeout'), options.**globalTimeout**);

Short circuit on certain exception types

Some exceptions may be due to a condition that won't change over the course of polling - for example, a syntax error.

The idea would be, if we fail on one of these types of exceptions, to stop polling early since we won't have any different results.

No way to cancel with specific error message

I'm wanting to make a function that polls a GitHub commit status until my CI build has finished. To do this with promise-poller, I have the following code:

taskFn: () => this.repos.owner(owner).repo(repo).commits.ref(ref).status().then(({state}) => {
  if (state === 'failure') return false
  if (state === 'pending') throw new Error('commit is still pending')
}),

This is kind of backwards, at least for my use case, because I can't return a "build failed" error if state === 'failure'. It would be better if I could return false when state === 'pending' and throw an error when state === 'failure'.

But really, considering that one might want to poll and eventually resolve to a boolean value, I think it would be best to support isDone and shouldContinue functions that default to the current behavior.

Exponential backoff support

Retrying after a fixed interval may not be ideal in some applications. Extending this package to also support using exponential backoff would be helpful.

Clear masterTimeout interval

masterTimeout is implemented using a setInterval that is not cleared when the master promise resolves. If the timeout is long and the program is supposed to exit after polling completes, the interval will prevent the Node process from exiting.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.