{"id":19732205,"url":"https://github.com/lifeomic/attempt","last_synced_at":"2025-05-16T05:00:27.285Z","repository":{"id":41094972,"uuid":"121335611","full_name":"lifeomic/attempt","owner":"lifeomic","description":"A JavaScript library that makes it easier to retry functions that return a promise","archived":false,"fork":false,"pushed_at":"2024-04-10T22:00:40.000Z","size":497,"stargazers_count":193,"open_issues_count":8,"forks_count":17,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-05-02T06:46:43.996Z","etag":null,"topics":["async","attempt","await","backoff","exponential-backoff","javascript","nodejs","promise","retry","retry-strategies","team-skillspring","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lifeomic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-13T03:47:09.000Z","updated_at":"2025-04-13T16:11:01.000Z","dependencies_parsed_at":"2024-02-21T13:24:49.301Z","dependency_job_id":"ef42de75-270c-404d-bb36-4f5e61275fbb","html_url":"https://github.com/lifeomic/attempt","commit_stats":{"total_commits":81,"total_committers":13,"mean_commits":6.230769230769231,"dds":0.7037037037037037,"last_synced_commit":"7d95a1fad9ddab2f89fd5bb2084f2d5a1237fca9"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Fattempt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Fattempt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Fattempt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Fattempt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lifeomic","download_url":"https://codeload.github.com/lifeomic/attempt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253267680,"owners_count":21881126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","attempt","await","backoff","exponential-backoff","javascript","nodejs","promise","retry","retry-strategies","team-skillspring","typescript"],"created_at":"2024-11-12T00:25:15.252Z","updated_at":"2025-05-16T05:00:27.240Z","avatar_url":"https://github.com/lifeomic.png","language":"TypeScript","readme":"# Attempt\n\n[![Greenkeeper badge](https://badges.greenkeeper.io/lifeomic/attempt.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/lifeomic/attempt.svg?branch=master)](https://travis-ci.org/lifeomic/attempt) [![Coverage Status](https://coveralls.io/repos/github/lifeomic/attempt/badge.svg?branch=master)](https://coveralls.io/github/lifeomic/attempt?branch=master) [![npm version](https://badge.fury.io/js/%40lifeomic%2Fattempt.svg)](https://badge.fury.io/js/%40lifeomic%2Fattempt)\n\nThis library exports a `retry(...)` function that can be used to invoke\na function that returns a `Promise` multiple times until returned\n`Promise` is resolved or the max number of attempts is reached.\n\nThe delay between each attempt is configurable and allows multiple\nretry strategies.\n\nThe following features are supported:\n\n- Fixed delay between attempts\n- Exponential backoff\n- Exponential backoff with [jitter](\u003chttps://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\u003e)\n- Abort retries early\n- Abort due to timeout\n- Error handler for each attempt\n\n## Installation\n\n**Using NPM:**\n```bash\nnpm i @lifeomic/attempt\n```\n\n**Using Yarn:**\n```bash\nyarn add @lifeomic/attempt\n```\n\n## Usage\n\n**Node.js / CommonJS:**\n\n```js\nconst retry = require('@lifeomic/attempt').retry;\n```\n\n**ES6 / TypeScript**\n\n```js\nimport { retry } from '@lifeomic/attempt';\n```\n\n```js\ntry {\n  const result = await retry(async (context) =\u003e {\n    // some code that returns a promise or resolved value\n  }, options);\n} catch (err) {\n  // If the max number of attempts was exceeded then `err`\n  // will be the last error that was thrown.\n  //\n  // If error is due to timeout then `err.code` will be the\n  // string `ATTEMPT_TIMEOUT`.\n}\n```\n\nThe `options` argument is optional, and when absent the default values\nare assigned. All times/durations are in milliseconds.\n\nThe following object shows the default options:\n\n```js\n{\n  delay: 200,\n  maxAttempts: 3,\n  initialDelay: 0,\n  minDelay: 0,\n  maxDelay: 0,\n  factor: 0,\n  timeout: 0,\n  jitter: false,\n  initialJitter: false,\n  handleError: null,\n  handleTimeout: null,\n  beforeAttempt: null,\n  calculateDelay: null\n}\n```\n\n**NOTE:**\n\nIf you are using a JavaScript runtime that doesn't support modern\nJavaScript features such as `async`/`await` then you will need to\nuse a transpiler such as `babel` to transpile the JavaScript code\nto your target environment.\n\n**Supported `options`:**\n\n- **`delay`**: `Number`\n\n  The delay between each attempt in milliseconds.\n  You can provide a `factor` to have the `delay` grow exponentially.\n\n  (default: `200`)\n\n- **`initialDelay`**: `Number`\n\n  The `intialDelay` is the amount of time to\n  wait before making the first attempt. This option should typically\n  be `0` since you typically want the first attempt to happen immediately.\n\n  (default: `0`)\n\n- **`maxDelay`**: `Number`\n\n  The `maxDelay` option is used to set an upper bound\n  for the delay when `factor` is enabled. A value of `0` can be provided\n  if there should be no upper bound when calculating delay.\n\n  (default: `0`)\n\n- **`factor`**: `Number`\n\n  The `factor` option is used to grow the `delay`\n  exponentially. For example, a value of `2` will cause the delay to\n  double each time. A value of `3` will cause the delay to triple\n  each time. Fractional factors (e.g. `1.5`) are also allowed.\n\n  The following formula is used to calculate delay using the factor:\n\n  `delay = delay * Math.pow(factor, attemptNum)`\n\n  (default: `0`)\n\n- **`maxAttempts`**: `Number`\n\n  The maximum number of attempts or `0` if there\n  is no limit on number of attempts.\n\n  (default: `3`)\n\n- **`timeout`**: `Number`\n\n  A timeout in milliseconds. If `timeout` is non-zero then a timer is\n  set using `setTimeout`. If the timeout is triggered then future attempts\n  will be aborted.\n\n  The `handleTimeout` function can be used to implement fallback\n  functionality.\n\n  (default: `0`)\n\n- **`jitter`**: `Boolean`\n\n  If `jitter` is `true` then the calculated delay will\n   be a random integer value between `minDelay` and the calculated delay\n   for the current iteration.\n\n   The following formula is used to calculate delay using `jitter`:\n\n   `delay = Math.random() * (delay - minDelay) + minDelay`\n\n   (default: `false`)\n\n- **`initialJitter`**: `Boolean`\n\n  If `initialJitter` is `true` then a `jitter` will also be used in the\n  first call attempt.\n\n   (default: `false`)\n\n- **`minDelay`**: `Number`\n\n  `minDelay` is used to set a lower bound of delay\n  when `jitter` is enabled. This property has no effect if `jitter`\n  is disabled.\n\n  (default: `0`)\n\n- **`handleError`**: `(err, context, options) =\u003e Promise\u003cvoid\u003e | void`\n\n  `handleError` is a function that will be invoked when an error occurs\n  for an attempt. The first argument is the error and the second\n  argument is the context.\n\n- **`handleTimeout`**: `(context, options) =\u003e Promise | void`\n\n  `handleTimeout` is invoked if a timeout occurs when using a non-zero\n  `timeout`. The `handleTimeout` function should return a `Promise`\n  that will be the return value of the `retry()` function.\n\n- **`beforeAttempt`**: `(context, options) =\u003e void`\n\n  The `beforeAttempt` function is invoked before each attempt.\n  Calling `context.abort()` will abort the attempt and stop retrying.\n\n- **`calculateDelay`**: `(context, options) =\u003e Number`\n\n  The `calculateDelay` function can be used to override the default\n  delay calculation. Your provided function should return an integer value\n  that is the calculated delay for a given attempt.\n\n  Information in the provided `context` and `options` arguments should be\n  used in the calculation.\n\n  When `calculateDelay` is provided,\n  any option that is used to calculate delay\n  (`delay`, `jitter`, `maxDelay`, `factor`, etc.) will be ignored.\n\n**The `context` has the following properties:**\n\n- **`attemptNum`**: `Number`\n\n  A zero-based index of the current attempt\n  number (`0`, `1`, `2`, etc.).\n\n- **`attemptsRemaining`**: `Number`\n\n  The number of attempts remaining. The initial value is `maxAttempts`\n  or `-1` if `maxAttempts` is `0` (unbounded).\n\n- **`abort`**: `() =\u003e void`\n\n  The `abort` function can be called when handling\n  an error via `handleError` or when `beforeAttempt` function is invoked.\n  The abort function should be used to prevent any further attempts in cases\n  when an error indicates that we should not retry.\n\n  For example, an HTTP request that returns an HTTP\n  error code of `400` (Bad Request) should not be retried because there is\n  a problem with the input (and retrying will not fix this).\n  However, a request that returns `504` (Gateway Timeout) should be\n  retried because it might be a temporary problem.\n\n## Recipes\n\n### Retry with defaults\n\n```js\n// Try the given operation up to 3 times with a delay of 200 between\n// each attempt\nconst result = await retry(async function() {\n  // do something that returns a promise\n});\n```\n\n### Stop retrying if an error indicates that we should not retry\n\n```js\n// Try the given operation update to 4 times. The initial delay will be 0\n// and subsequent delays will be 200, 400, 800\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 4,\n  handleError (err, context) {\n    if (err.retryable === false) {\n      // We should abort because error indicates that request is not retryable\n      context.abort();\n    }\n  }\n});\n```\n\n### Retry with exponential backoff\n\n```js\n// Try the given operation update to 4 times. The initial delay will be 0\n// and subsequent delays will be 200, 400, 800 (delay doubles each time due\n// to factor of `2`)\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 4\n});\n```\n\n### Retry with exponential backoff and max delay\n\n```js\n// Try the given operation up to 5 times. The initial delay will be 0\n// and subsequent delays will be 200, 400, 500, 500 (capped at `maxDelay`)\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 5,\n  maxDelay: 500\n});\n```\n\n### Retry with exponential backoff, jitter, min delay, and max delay\n\n```js\n// Try the given operation 3 times. The initial delay will be 0\n// and subsequent delays will be in the following range:\n// - 100 to 200\n// - 100 to 400\n// - 100 to 500 (capped at `maxDelay`)\n// - 100 to 500 (capped at `maxDelay`)\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 5,\n  minDelay: 100,\n  maxDelay: 500,\n  jitter: true\n});\n```\n\n### Stop retrying if there is a timeout\n\n```js\n// Try the given operation up to 5 times. The initial delay will be 0\n// and subsequent delays will be 200, 400, 800, 1600.\n//\n// If an attempt fails to complete after 1 second then the retries\n// are aborted and error with `code` `ATTEMPT_TIMEOUT` is thrown.\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 5,\n  timeout: 1000\n});\n```\n\n### Stop retrying if there is a timeout but provide a fallback\n\n```js\n// Try the given operation up to 5 times. The initial delay will be 0\n// and subsequent delays will be 200, 400, 800, 1600.\n//\n// If an attempt fails to complete after 1 second then the retries\n// are aborted and the `handleTimeout` implements some fallback logic.\nconst result = await retry(async function() {\n  // do something that returns a promise\n}, {\n  delay: 200,\n  factor: 2,\n  maxAttempts: 5,\n  timeout: 1000,\n  async handleTimeout (context) {\n    // do something that returns a promise or throw your own error\n  }\n});\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeomic%2Fattempt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flifeomic%2Fattempt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeomic%2Fattempt/lists"}