├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── auto-merge-dependabot.yml ├── .gitignore ├── .mergify.yml ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── d.ts └── polly-js.d.ts ├── examples └── browser │ └── jQuery.html ├── gulpfile.js ├── images ├── polly-js-120.png └── polly-js-1280.png ├── package-lock.json ├── package.json ├── src └── polly.js └── tests ├── hello.txt ├── retry-execute-async-node.js ├── retry-execute-promise.js ├── retry-execute.js ├── wait-retry-execute-async-node.js └── wait-retry-execute-promise.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [package.json] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-Merge oldest Dependabot PR 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 5 * * *' # At 5:00 on every day-of-week 7 | 8 | jobs: 9 | merge: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup GitHub CLI 16 | run: | 17 | gh auth login --with-token <<< "${{ secrets.DEPENDABOT_MERGE_PAT }}" 18 | 19 | - name: Find oldest mergeable Dependabot PR 20 | id: find_pr 21 | run: | 22 | PR_URL=$(gh pr list --author dependabot[bot] --json url,mergeStateStatus --jq '.[] | select(.mergeStateStatus == "CLEAN") | .url' | tail -n 1) 23 | if [ -z "$PR_URL" ]; then 24 | echo "No eligible PRs found" 25 | gh pr list --author dependabot[bot] --json url,mergeStateStatus 26 | exit 0 27 | fi 28 | echo "Selected PR ${PR_URL} for merge" 29 | echo "PR_URL=${PR_URL}" >> $GITHUB_OUTPUT 30 | 31 | - name: Merge PR 32 | if: steps.find_pr.outputs.PR_URL 33 | run: gh pr merge --auto --merge "${{ steps.find_pr.outputs.PR_URL }}" 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge for Greenkeeper pull requests 3 | conditions: 4 | - author=greenkeeper[bot] 5 | - status-success=continuous-integration/travis-ci/pr 6 | - status-success=greenkeeper/verify 7 | actions: 8 | merge: 9 | method: merge 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "16" 5 | - "15" 6 | - "14" 7 | - "12" 8 | after_success: 9 | - npm run report-coverage 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Maurice de Beijer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # polly-js 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/mauricedb/polly-js.svg)](https://greenkeeper.io/) 4 | Transient exception handling for JavaScript made easy. 5 | 6 | [![npm version](https://img.shields.io/npm/v/polly-js.svg?style=flat-square)](https://www.npmjs.org/package/polly-js) 7 | [![Bower](https://img.shields.io/bower/v/bootstrap.svg?maxAge=2592000)](https://github.com/mauricedb/polly-js) 8 | [![npm downloads](https://img.shields.io/npm/dm/polly-js.svg?style=flat-square)](http://npm-stat.com/charts.html?package=polly-js&from=2015-09-01) 9 | [![Dependency Status](https://david-dm.org/mauricedb/polly-js.svg)](https://david-dm.org/mauricedb/polly-js) 10 | [![Build Status](https://travis-ci.org/mauricedb/polly-js.svg?branch=master)](https://travis-ci.org/mauricedb/polly-js) 11 | [![codecov.io](http://codecov.io/github/mauricedb/polly-js/coverage.svg?branch=master)](http://codecov.io/github/mauricedb/polly-js?branch=master) 12 | 13 | Polly-js is a library to help developers recover from transient errors using policies like retry or wait and retry. 14 | 15 | ![Polly-js](https://raw.github.com/mauricedb/polly-js/master/images/polly-js-120.png) 16 | 17 | The typical use case for polly-js are retrying actions after they fail. These actions often include some form of IO which can fail. 18 | Examples of these IO actions are AJAX requests to other services, file operations on disk or interacting with a database. 19 | All these actions share the characteristics that they can occasionally fail because of circumstances outside of the control of your application like the network connection might be briefly unavailable or the file might be in use by another process. 20 | When any of these actions fail there is a change that just retrying the same action, possibly after a short delay, will work. Polly-js makes these retry actions easy to code. 21 | 22 | ## Detecting failures 23 | Depending on the function being executed different ways of detecting failure are used. 24 | 25 | When you call ```polly().execute()``` the code assumes that your code failed and needs to be retried when your function throws an Error. 26 | 27 | When you call ```polly().executeForPromise()``` the code assumes that your code failed and needs to be retried when your function returns a Promise that is rejected. 28 | 29 | When you call ```polly().executeForNode()``` the code assumes that your code failed and needs to be retried when your function calls the callback with a first non null parameter indicating an error. 30 | 31 | ## Deciding what to do on failure 32 | Whenever a failure is detected Polly-js will attempt to retry your action. 33 | 34 | You get to control how a failure is retried by calling either ```polly().retry()``` or ```polly().waitAndRetry()```. 35 | Both will retry the failing action but the ```polly().waitAndRetry()``` option adds a small delay before retrying. 36 | In general ```polly().waitAndRetry()``` is probably the more appropriate policy to use as retrying without apause could cause a lot requests. 37 | By default both will retry once where ```polly().waitAndRetry()``` waits 100 ms before retrying. With either function you can specify a number of retries to attempt on repeated failures. 38 | With ```polly().waitAndRetry()``` it will double the time between each try. If you want to control the exact delays you can also specify and array with the individual delay values so ```polly().waitAndRetry(5)``` is equivalent to ```polly().waitAndRetry([100, 200, 400, 800, 1600])```. 39 | 40 | ## Deciding what failures to retry 41 | Using ```polly().handle()``` you can decide if you want to retry a specific failure. For example you might want to retry an AJAX request returning a 404 response code but not one resulting in a 500 response code. 42 | The callback function specified in ```polly().handle()``` is called with watever error was received so you can use any property from there and you return true if you want to retry the action or false to stop retrying. 43 | 44 | ## Usage 45 | 46 | Try to load the Google home page and retry twice if it fails. 47 | 48 | ```JavaScript 49 | polly() 50 | .retry(2) 51 | .executeForPromise(function () { 52 | return requestPromise('http://www.google.com'); 53 | }) 54 | .then(function(result) { 55 | console.log(result) 56 | }, function(err) { 57 | console.error('Failed trying three times', err) 58 | }); 59 | ``` 60 | 61 | Try to read a file from disk and retry twice if this fails. 62 | 63 | ```JavaScript 64 | polly() 65 | .retry(2) 66 | .executeForNode(function (cb) { 67 | fs.readFile(path.join(__dirname, './hello.txt'), cb); 68 | }, function (err, data) { 69 | if (err) { 70 | console.error('Failed trying three times', err) 71 | } else { 72 | console.log(data) 73 | } 74 | }); 75 | ``` 76 | 77 | Only retry 'no such file or directory' errors. Wait 100 ms before retrying. 78 | 79 | ```JavaScript 80 | polly() 81 | .handle(function(err) { 82 | return err.code === 'ENOENT'; 83 | }) 84 | .waitAndRetry() 85 | .executeForNode(function (cb) { 86 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 87 | }, function (err, data) { 88 | if (err) { 89 | console.error('Failed trying twice with a 100ms delay', err) 90 | } else { 91 | console.log(data) 92 | } 93 | }); 94 | ``` 95 | 96 | Use async await with the browsers `fetch` API. 97 | 98 | ```JavaScript 99 | const loadData = url => { 100 | return polly() 101 | .waitAndRetry(2) 102 | .executeForPromise(async () => { 103 | const rsp = await fetch(url); 104 | if (rsp.ok) { 105 | return rsp.json(); 106 | } 107 | return Promise.reject(rsp); 108 | }); 109 | }; 110 | 111 | // Using the function somewhere else: 112 | const movies = await loadData("http://localhost:3000/movies.json"); 113 | ``` 114 | 115 | ## Logging errors 116 | You can set a logger function to be called every time an error is detected. 117 | ```JavaScript 118 | polly() 119 | .logger(function(err){ 120 | console.error(err); //will be hit 2 times 121 | }) 122 | .retry(2) 123 | .executeForPromise(function () { 124 | return requestPromise('http://foo.bar.com'); 125 | }) 126 | .then(function(result) { 127 | //do some important stuff here 128 | }, function(err) { 129 | console.error('Failed trying three times', err);//third error is passed back to the caller 130 | }); 131 | ``` 132 | 133 | ## Acknowledgements 134 | 135 | The library is based on the [Polly NuGet package](https://www.nuget.org/packages/Polly/) by Michael Wolfenden 136 | 137 | 138 | ## See Also 139 | 140 | - [Cockatiel](https://www.npmjs.com/package/cockatiel): A more feature rich package that includes polies like Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback. 141 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polly-js", 3 | "description": "Transient exception handling", 4 | "main": "src/polly.js", 5 | "authors": [ 6 | "Maurice de Beijer" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "transient", 11 | "exception", 12 | "handling", 13 | "error", 14 | "try", 15 | "catch", 16 | "promise" 17 | ], 18 | "homepage": "https://github.com/mauricedb/polly-js", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /d.ts/polly-js.d.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | // Type definitions for polly-js 3 | 4 | declare module 'polly-js' { 5 | function polly (): polly.Polly; 6 | 7 | namespace polly { 8 | type Info = { 9 | count: number; 10 | }; 11 | 12 | interface AsyncRetryable { 13 | executeForPromise (fn: (info: Info) => Promise): Promise; 14 | executeForNode ( 15 | fn: (cb: (err?: object, data?: any) => any, info: Info) => any, 16 | cb?: (err?: object, data?: any) => any 17 | ): void; 18 | } 19 | 20 | interface Retryable extends AsyncRetryable { 21 | execute (fn: (info: Info) => any): any; 22 | } 23 | 24 | interface Polly { 25 | logger (fn: (err: any) => void): Polly; 26 | handle (fn: (err: any) => boolean): Polly; 27 | retry (numRetries: number): Retryable; 28 | waitAndRetry (delays: number[]): AsyncRetryable; 29 | waitAndRetry (numRetries: number): AsyncRetryable; 30 | } 31 | 32 | type PollyDefaults = { 33 | delay: number; 34 | } 35 | 36 | const defaults: PollyDefaults; 37 | } 38 | 39 | export = polly; 40 | } 41 | -------------------------------------------------------------------------------- /examples/browser/jQuery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Using polly-js with jQuery

10 | 11 | 12 | 13 |
    14 | 15 | 16 | 17 | 18 | 51 | 52 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by maurice on 9/17/2015. 3 | */ 4 | 5 | var gulp = require('gulp'); 6 | var mocha = require('gulp-mocha'); 7 | 8 | gulp.task('mocha', function () { 9 | return gulp.src('tests/**/*.js', {read: false}) 10 | .pipe(mocha()); 11 | }); 12 | 13 | gulp.task('watch', function () { 14 | gulp.watch('**/*.js', ['mocha']); 15 | }); 16 | 17 | gulp.task('default', ['mocha', 'watch']); -------------------------------------------------------------------------------- /images/polly-js-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauricedb/polly-js/70e02e13a5a3f0bdab81d5d6bd03a5ced050e0a4/images/polly-js-120.png -------------------------------------------------------------------------------- /images/polly-js-1280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauricedb/polly-js/70e02e13a5a3f0bdab81d5d6bd03a5ced050e0a4/images/polly-js-1280.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polly-js", 3 | "version": "1.8.3", 4 | "description": "Transient exception handling", 5 | "main": "src/polly.js", 6 | "typings": "d.ts/polly-js.d.ts", 7 | "scripts": { 8 | "test": "istanbul cover ./node_modules/mocha/bin/_mocha tests", 9 | "test:watch": "mocha tests --watch", 10 | "report-coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/codecov" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mauricedb/polly-js.git" 15 | }, 16 | "keywords": [ 17 | "transient", 18 | "exception", 19 | "handling", 20 | "error", 21 | "try", 22 | "catch", 23 | "promise" 24 | ], 25 | "author": "Maurice de Beijer", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/mauricedb/polly-js/issues" 29 | }, 30 | "homepage": "https://github.com/mauricedb/polly-js#readme", 31 | "devDependencies": { 32 | "chai": "5.2.0", 33 | "chai-as-promised": "8.0.1", 34 | "codecov.io": "0.1.6", 35 | "gulp": "5.0.1", 36 | "gulp-mocha": "10.0.1", 37 | "istanbul": "0.4.5", 38 | "mocha": "11.6.0", 39 | "request": "^2.88.2", 40 | "request-promise": "4.2.6" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/polly.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by maurice on 9/17/2015. 3 | */ 4 | 5 | (function (root, factory) { 6 | if (typeof define === "function" && define.amd) { 7 | // AMD. Register as an anonymous module. 8 | define([], factory); 9 | } else if (typeof exports === "object") { 10 | // Node. Does not work with strict CommonJS, but 11 | // only CommonJS-like environments that support module.exports, 12 | // like Node. 13 | module.exports = factory(); 14 | } else { 15 | // Browser globals (root is window) 16 | root.polly = factory(); 17 | } 18 | })(this, function () { 19 | "use strict"; 20 | 21 | var defaults = { 22 | delay: 100, 23 | }; 24 | 25 | function execute(config, cb) { 26 | var count = 0; 27 | 28 | while (true) { 29 | try { 30 | return cb({ count: count }); 31 | } catch (ex) { 32 | if (count < config.count && config.handleFn(ex)) { 33 | config.loggerFn(ex); 34 | count++; 35 | } else { 36 | throw ex; 37 | } 38 | } 39 | } 40 | } 41 | 42 | function executeForPromise(config, cb) { 43 | var count = 0; 44 | 45 | return new Promise(function (resolve, reject) { 46 | function execute() { 47 | var original = cb({ count: count }); 48 | 49 | original.then( 50 | function (e) { 51 | resolve(e); 52 | }, 53 | function (e) { 54 | if (count < config.count && config.handleFn(e)) { 55 | config.loggerFn(e); 56 | count++; 57 | execute(); 58 | } else { 59 | reject(e); 60 | } 61 | } 62 | ); 63 | } 64 | 65 | execute(); 66 | }); 67 | } 68 | 69 | function executeForPromiseWithDelay(config, cb) { 70 | var count = 0; 71 | 72 | return new Promise(function (resolve, reject) { 73 | function execute() { 74 | var original = cb({ count: count }); 75 | 76 | original.then( 77 | function (e) { 78 | resolve(e); 79 | }, 80 | function (e) { 81 | var delay = config.delays.shift(); 82 | 83 | if (delay && config.handleFn(e)) { 84 | config.loggerFn(e); 85 | count++; 86 | setTimeout(execute, delay); 87 | } else { 88 | reject(e); 89 | } 90 | } 91 | ); 92 | } 93 | 94 | execute(); 95 | }); 96 | } 97 | 98 | function executeForNode(config, fn, callback) { 99 | var count = 0; 100 | 101 | function internalCallback(err, data) { 102 | if (err && count < config.count && config.handleFn(err)) { 103 | config.loggerFn(err); 104 | count++; 105 | fn(internalCallback, { count: count }); 106 | } else { 107 | callback(err, data); 108 | } 109 | } 110 | 111 | fn(internalCallback, { count: count }); 112 | } 113 | 114 | function executeForNodeWithDelay(config, fn, callback) { 115 | var count = 0; 116 | 117 | function internalCallback(err, data) { 118 | var delay = config.delays.shift(); 119 | if (err && delay && config.handleFn(err)) { 120 | config.loggerFn(err); 121 | count++; 122 | setTimeout(function () { 123 | fn(internalCallback, { count: count }); 124 | }, delay); 125 | } else { 126 | callback(err, data); 127 | } 128 | } 129 | 130 | fn(internalCallback, { count: count }); 131 | } 132 | 133 | function delayCountToDelays(count) { 134 | var delays = [], 135 | delay = defaults.delay; 136 | 137 | for (var i = 0; i < count; i++) { 138 | delays.push(delay); 139 | delay = 2 * delay; 140 | } 141 | 142 | return delays; 143 | } 144 | 145 | var pollyFn = function () { 146 | var config = { 147 | count: 1, 148 | delays: [defaults.delay], 149 | handleFn: function () { 150 | return true; 151 | }, 152 | loggerFn: function (err) {}, 153 | }; 154 | 155 | return { 156 | logger: function (loggerFn) { 157 | if (typeof loggerFn === "function") { 158 | config.loggerFn = loggerFn; 159 | } 160 | 161 | return this; 162 | }, 163 | handle: function (handleFn) { 164 | if (typeof handleFn === "function") { 165 | config.handleFn = handleFn; 166 | } 167 | 168 | return this; 169 | }, 170 | retry: function (count) { 171 | if (typeof count === "number") { 172 | config.count = count; 173 | } 174 | 175 | return { 176 | execute: execute.bind(null, config), 177 | executeForPromise: executeForPromise.bind(null, config), 178 | executeForNode: executeForNode.bind(null, config), 179 | }; 180 | }, 181 | waitAndRetry: function (delays) { 182 | if (typeof delays === "number") { 183 | delays = delayCountToDelays(delays); 184 | } 185 | 186 | if (Array.isArray(delays)) { 187 | config.delays = delays; 188 | } 189 | 190 | return { 191 | executeForPromise: executeForPromiseWithDelay.bind( 192 | null, 193 | config 194 | ), 195 | executeForNode: executeForNodeWithDelay.bind(null, config), 196 | }; 197 | }, 198 | }; 199 | }; 200 | pollyFn.defaults = defaults; 201 | 202 | return pollyFn; 203 | }); 204 | -------------------------------------------------------------------------------- /tests/hello.txt: -------------------------------------------------------------------------------- 1 | Hello world -------------------------------------------------------------------------------- /tests/retry-execute-async-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var chaiAsPromised = require('chai-as-promised'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | 8 | chai.use(chaiAsPromised); 9 | var should = chai.should(); 10 | 11 | var polly = require('..'); 12 | 13 | describe('The retry policy with a asynchronous node call', function () { 14 | it('should return the result when no error', function (done) { 15 | 16 | polly() 17 | .retry() 18 | .executeForNode(function (cb, {count}) { 19 | fs.readFile(path.join(__dirname, './hello.txt'), cb); 20 | }, function (err, data) { 21 | should.not.exist(err); 22 | data.toString().should.equal('Hello world'); 23 | done(); 24 | }); 25 | }); 26 | 27 | it('should reject after an error', function (done) { 28 | 29 | polly() 30 | .retry() 31 | .executeForNode(function (cb, {count}) { 32 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 33 | }, function (err, data) { 34 | should.exist(err); 35 | err.should.be.instanceof(Error); 36 | should.not.exist(data); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('should retry once after an error and still fail', function (done) { 42 | var actualRetryCount = 0; 43 | 44 | polly() 45 | .retry() 46 | .executeForNode(function (cb, {count}) { 47 | actualRetryCount = count; 48 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 49 | }, function (err, data) { 50 | should.exist(err); 51 | err.should.be.instanceof(Error); 52 | should.not.exist(data); 53 | actualRetryCount.should.equal(1); 54 | done(); 55 | }); 56 | }); 57 | 58 | it('should retry five times after an error and still fail', function (done) { 59 | var actualRetryCount = 0; 60 | 61 | polly() 62 | .retry(5) 63 | .executeForNode(function (cb, {count}) { 64 | actualRetryCount = count; 65 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 66 | }, function (err, data) { 67 | should.exist(err); 68 | err.should.be.instanceof(Error); 69 | should.not.exist(data); 70 | actualRetryCount.should.equal(5); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('should retry once after an error and succeed', function (done) { 76 | var actualRetryCount = 0; 77 | 78 | polly() 79 | .retry() 80 | .executeForNode(function (cb, {count}) { 81 | 82 | actualRetryCount = count; 83 | if (count < 1) { 84 | cb(new Error("Wrong value")); 85 | } else { 86 | cb(undefined, 42); 87 | } 88 | }, function (err, data) { 89 | should.not.exist(err); 90 | data.should.equal(42); 91 | actualRetryCount.should.equal(1); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should retry four times after an error and succeed', function (done) { 97 | var actualRetryCount = 0; 98 | 99 | polly() 100 | .retry(5) 101 | .executeForNode(function (cb, {count}) { 102 | actualRetryCount = count; 103 | if (count < 4) { 104 | cb(new Error("Wrong value")); 105 | } else { 106 | cb(undefined, 42); 107 | } 108 | }, function (err, data) { 109 | should.not.exist(err); 110 | data.should.equal(42); 111 | actualRetryCount.should.equal(4); 112 | done(); 113 | }); 114 | }); 115 | 116 | it('should retry five times if handling the error after an error and still fail', function (done) { 117 | var actualRetryCount = 0; 118 | 119 | polly() 120 | .handle(function(){ 121 | return true; 122 | }) 123 | .retry(5) 124 | .executeForNode(function (cb, {count}) { 125 | actualRetryCount = count; 126 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 127 | }, function (err, data) { 128 | should.exist(err); 129 | err.should.be.instanceof(Error); 130 | should.not.exist(data); 131 | actualRetryCount.should.equal(5); 132 | done(); 133 | }); 134 | }); 135 | 136 | it('should not retry if not handling the error and still fail', function (done) { 137 | var actualRetryCount = 0; 138 | 139 | polly() 140 | .handle(function(){ 141 | return false; 142 | }) 143 | .retry(5) 144 | .executeForNode(function (cb, {count}) { 145 | actualRetryCount = count; 146 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 147 | }, function (err, data) { 148 | should.exist(err); 149 | err.should.be.instanceof(Error); 150 | should.not.exist(data); 151 | actualRetryCount.should.equal(0); 152 | done(); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /tests/retry-execute-promise.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var chai = require("chai"); 4 | var chaiAsPromised = require("chai-as-promised"); 5 | var requestPromise = require("request-promise"); 6 | 7 | chai.use(chaiAsPromised); 8 | chai.should(); 9 | 10 | var polly = require(".."); 11 | 12 | describe("The retry policy with a asynchronous promise call", function () { 13 | it("should return the result when no error", function () { 14 | return polly() 15 | .retry() 16 | .executeForPromise(function () { 17 | return Promise.resolve(42); 18 | }) 19 | .should.eventually.equal(42); 20 | }); 21 | 22 | it("should reject after an error", function () { 23 | return polly() 24 | .retry() 25 | .executeForPromise(function () { 26 | return Promise.reject(new Error("Wrong value")); 27 | }) 28 | .should.eventually.be.rejectedWith(Error, "Wrong value"); 29 | }); 30 | 31 | it("should retry once after an error and still fail", function () { 32 | var actualRetryCount = 0; 33 | 34 | return polly() 35 | .retry() 36 | .executeForPromise(function ({ count }) { 37 | return new Promise(function (resolve, reject) { 38 | actualRetryCount = count; 39 | reject(new Error("Wrong value")); 40 | }); 41 | }) 42 | .should.eventually.be.rejected.then(function () { 43 | actualRetryCount.should.equal(1); 44 | }); 45 | }); 46 | 47 | it("should retry five times after an error and still fail", function () { 48 | var actualRetryCount = 0; 49 | 50 | return polly() 51 | .retry(5) 52 | .executeForPromise(function ({ count }) { 53 | return new Promise(function (resolve, reject) { 54 | actualRetryCount = count; 55 | reject(new Error("Wrong value")); 56 | }); 57 | }) 58 | .should.eventually.be.rejected.then(function () { 59 | actualRetryCount.should.equal(5); 60 | }); 61 | }); 62 | 63 | it("should retry once after an error and succeed", function () { 64 | var actualRetryCount = 0; 65 | 66 | return polly() 67 | .retry() 68 | .executeForPromise(function ({ count }) { 69 | return new Promise(function (resolve, reject) { 70 | actualRetryCount = count; 71 | if (count < 1) { 72 | reject(new Error("Wrong value")); 73 | } else { 74 | resolve(42); 75 | } 76 | }); 77 | }) 78 | .should.eventually.equal(42) 79 | .then(function () { 80 | actualRetryCount.should.equal(1); 81 | }); 82 | }); 83 | 84 | it("should retry four times after an error and succeed", function () { 85 | var actualRetryCount = 0; 86 | 87 | return polly() 88 | .retry(5) 89 | .executeForPromise(function ({ count }) { 90 | return new Promise(function (resolve, reject) { 91 | actualRetryCount = count; 92 | if (count < 4) { 93 | reject(new Error("Wrong value")); 94 | } else { 95 | resolve(42); 96 | } 97 | }); 98 | }) 99 | .should.eventually.equal(42) 100 | .then(function () { 101 | actualRetryCount.should.equal(4); 102 | }); 103 | }); 104 | 105 | it("we can load html from Google", function () { 106 | var actualRetryCount = 0; 107 | 108 | return polly() 109 | .retry() 110 | .executeForPromise(function ({ count }) { 111 | actualRetryCount = count; 112 | return requestPromise("http://www.google.com"); 113 | }) 114 | .should.eventually.be.fulfilled.then(function () { 115 | actualRetryCount.should.equal(0); 116 | }); 117 | }); 118 | 119 | it("we can't load html from an invalid URL", function () { 120 | var actualRetryCount = 0; 121 | 122 | return polly() 123 | .retry() 124 | .executeForPromise(function ({ count }) { 125 | actualRetryCount = count; 126 | return requestPromise("http://www.this-is-no-site.com"); 127 | }) 128 | .should.eventually.be.rejected.then(function () { 129 | actualRetryCount.should.equal(1); 130 | }); 131 | }); 132 | 133 | it("should retry five times if handling the error after an error and still fail", function () { 134 | var actualRetryCount = 0; 135 | 136 | return polly() 137 | .handle(function () { 138 | return true; 139 | }) 140 | .retry(5) 141 | .executeForPromise(function ({ count }) { 142 | return new Promise(function (resolve, reject) { 143 | actualRetryCount = count; 144 | reject(new Error("Wrong value")); 145 | }); 146 | }) 147 | .should.eventually.be.rejected.then(function () { 148 | actualRetryCount.should.equal(5); 149 | }); 150 | }); 151 | 152 | it("should not retry if not handling the error and still fail", function () { 153 | var actualRetryCount = 0; 154 | 155 | return polly() 156 | .handle(function () { 157 | return false; 158 | }) 159 | .retry(5) 160 | .executeForPromise(function ({ count }) { 161 | return new Promise(function (resolve, reject) { 162 | actualRetryCount = count; 163 | reject(new Error("Wrong value")); 164 | }); 165 | }) 166 | .should.eventually.be.rejected.then(function () { 167 | actualRetryCount.should.equal(0); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /tests/retry-execute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var polly = require('..'); 5 | 6 | describe('The retry policy with a synchronous call', function () { 7 | it('should return the result when no error', function () { 8 | 9 | var result = polly() 10 | .retry() 11 | .execute(function ({count}) { 12 | count.should.equal(1); 13 | return 42; 14 | }); 15 | 16 | result.should.equal(42); 17 | }); 18 | 19 | it('should throw after an error', function () { 20 | 21 | (function () { 22 | polly() 23 | .retry() 24 | .execute(function () { 25 | throw new Error("Wrong value"); 26 | }); 27 | }).should.Throw(Error, 'Wrong value'); 28 | }); 29 | 30 | it('should retry once after an error and still fail', function () { 31 | var actualRetryCount = 0; 32 | 33 | try { 34 | polly() 35 | .retry() 36 | .execute(function ({count}) { 37 | actualRetryCount = count; 38 | throw new Error("Wrong value"); 39 | }); 40 | } 41 | catch (ex) { 42 | } 43 | 44 | actualRetryCount.should.equal(1); 45 | }); 46 | 47 | it('should retry five times after an error and still fail', function () { 48 | var actualRetryCount = 0; 49 | 50 | try { 51 | polly() 52 | .retry(5) 53 | .execute(function ({count}) { 54 | actualRetryCount = count; 55 | throw new Error("Wrong value"); 56 | }); 57 | } 58 | catch (ex) { 59 | } 60 | 61 | actualRetryCount.should.equal(5); 62 | }); 63 | 64 | it('should retry once after an error and succeed', function () { 65 | var actualRetryCount = 0; 66 | 67 | var result = polly() 68 | .retry() 69 | .execute(function ({count}) { 70 | actualRetryCount = count; 71 | if (count < 1) { 72 | throw new Error("Wrong value"); 73 | } 74 | 75 | return 42; 76 | }); 77 | 78 | result.should.equal(42); 79 | actualRetryCount.should.equal(1); 80 | }); 81 | 82 | it('should retry four after an error and succeed', function () { 83 | var actualRetryCount = 0; 84 | 85 | var result = polly() 86 | .retry(5) 87 | .execute(function ({count}) { 88 | actualRetryCount = count; 89 | if (count < 5) { 90 | throw new Error("Wrong value"); 91 | } 92 | 93 | return 42; 94 | }); 95 | 96 | result.should.equal(42); 97 | actualRetryCount.should.equal(5); 98 | }); 99 | 100 | it('should retry five times after an error and still fail when all should be handled', function () { 101 | var actualRetryCount = 0; 102 | 103 | try { 104 | polly() 105 | .handle(function() { 106 | return true; 107 | }) 108 | .retry(5) 109 | .execute(function ({count}) { 110 | actualRetryCount = count; 111 | throw new Error("Wrong value"); 112 | }); 113 | } 114 | catch (ex) { 115 | } 116 | 117 | actualRetryCount.should.equal(5); 118 | }); 119 | 120 | it('should not retry times after an error and still fail when none should be handled', function () { 121 | var actualRetryCount = 0; 122 | 123 | try { 124 | polly() 125 | .handle(function() { 126 | return false; 127 | }) 128 | .retry(5) 129 | .execute(function ({count}) { 130 | actualRetryCount = count; 131 | throw new Error("Wrong value"); 132 | }); 133 | } 134 | catch (ex) { 135 | } 136 | 137 | actualRetryCount.should.equal(0); 138 | }); 139 | 140 | it('should retry 2 times after an error and still fail when all should be handled', function () { 141 | var actualRetryCount = 0; 142 | 143 | try { 144 | polly() 145 | .handle(function() { 146 | return actualRetryCount < 2; 147 | }) 148 | .retry(5) 149 | .execute(function ({count}) { 150 | actualRetryCount = count; 151 | throw new Error("Wrong value"); 152 | }); 153 | } 154 | catch (ex) { 155 | } 156 | 157 | actualRetryCount.should.equal(2); 158 | }); 159 | 160 | it('ignore the handle call if it isnt parameter a function', function () { 161 | var actualRetryCount = 0; 162 | 163 | try { 164 | polly() 165 | .handle() 166 | .retry(5) 167 | .execute(function ({count}) { 168 | actualRetryCount = count; 169 | throw new Error("Wrong value"); 170 | }); 171 | } 172 | catch (ex) { 173 | } 174 | 175 | actualRetryCount.should.equal(5); 176 | }); 177 | }); 178 | 179 | -------------------------------------------------------------------------------- /tests/wait-retry-execute-async-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var chaiAsPromised = require('chai-as-promised'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | 8 | chai.use(chaiAsPromised); 9 | var should = chai.should(); 10 | 11 | var polly = require('..'); 12 | 13 | describe('The wait and retry policy with a asynchronous node call', function () { 14 | beforeEach(function () { 15 | polly.defaults.delay = 1; 16 | }); 17 | 18 | it('should return the result when no error', function (done) { 19 | 20 | polly() 21 | .waitAndRetry() 22 | .executeForNode(function (cb) { 23 | fs.readFile(path.join(__dirname, './hello.txt'), cb); 24 | }, function (err, data) { 25 | should.not.exist(err); 26 | data.toString().should.equal('Hello world'); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('should reject after an error', function (done) { 32 | 33 | polly() 34 | .waitAndRetry() 35 | .executeForNode(function (cb) { 36 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 37 | }, function (err, data) { 38 | should.exist(err); 39 | err.should.be.instanceof(Error); 40 | should.not.exist(data); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should retry once after an error and still fail', function (done) { 46 | var actualRetryCount = 0; 47 | 48 | polly() 49 | .waitAndRetry() 50 | .executeForNode(function (cb, {count}) { 51 | actualRetryCount = count; 52 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 53 | }, function (err, data) { 54 | should.exist(err); 55 | err.should.be.instanceof(Error); 56 | should.not.exist(data); 57 | actualRetryCount.should.equal(1); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('should retry five times after an error and still fail', function (done) { 63 | var actualRetryCount = 0; 64 | 65 | polly() 66 | .waitAndRetry([1, 1, 1, 1, 1]) 67 | .executeForNode(function (cb, {count}) { 68 | actualRetryCount = count; 69 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 70 | }, function (err, data) { 71 | should.exist(err); 72 | err.should.be.instanceof(Error); 73 | should.not.exist(data); 74 | actualRetryCount.should.equal(5); 75 | done(); 76 | }); 77 | }); 78 | 79 | it('should retry five times after an error and still fail', function (done) { 80 | var actualRetryCount = 0; 81 | 82 | polly() 83 | .waitAndRetry(5) 84 | .executeForNode(function (cb, {count}) { 85 | actualRetryCount = count; 86 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 87 | }, function (err, data) { 88 | should.exist(err); 89 | err.should.be.instanceof(Error); 90 | should.not.exist(data); 91 | actualRetryCount.should.equal(5); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should retry once after an error and succeed', function (done) { 97 | var actualRetryCount = 0; 98 | 99 | polly() 100 | .waitAndRetry() 101 | .executeForNode(function (cb, {count}) { 102 | 103 | actualRetryCount = count; 104 | if (actualRetryCount < 1) { 105 | cb(new Error("Wrong value")); 106 | } else { 107 | cb(undefined, 42); 108 | } 109 | }, function (err, data) { 110 | should.not.exist(err); 111 | data.should.equal(42); 112 | actualRetryCount.should.equal(1); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('should retry four times after an error and succeed', function (done) { 118 | var actualRetryCount = 0; 119 | 120 | polly() 121 | .waitAndRetry(5) 122 | .executeForNode(function (cb, {count}) { 123 | actualRetryCount = count; 124 | if (actualRetryCount < 4) { 125 | cb(new Error("Wrong value")); 126 | } else { 127 | cb(undefined, 42); 128 | } 129 | }, function (err, data) { 130 | should.not.exist(err); 131 | data.should.equal(42); 132 | actualRetryCount.should.equal(4); 133 | done(); 134 | }); 135 | }); 136 | 137 | it('should retry once because we are handling the no such file or directory error', function (done) { 138 | var actualRetryCount = 0; 139 | 140 | polly() 141 | .handle(function(err) { 142 | return err.code === 'ENOENT' 143 | }) 144 | .waitAndRetry() 145 | .executeForNode(function (cb, {count}) { 146 | actualRetryCount = count; 147 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 148 | }, function (err, data) { 149 | should.exist(err); 150 | err.should.be.instanceof(Error); 151 | should.not.exist(data); 152 | actualRetryCount.should.equal(1); 153 | done(); 154 | }); 155 | }); 156 | 157 | it('should not retry because we are not handling the no such file or directory error', function (done) { 158 | var actualRetryCount = 0; 159 | 160 | polly() 161 | .handle(function(err) { 162 | return err.code !== 'ENOENT' 163 | }) 164 | .waitAndRetry() 165 | .executeForNode(function (cb, {count}) { 166 | actualRetryCount = count; 167 | fs.readFile(path.join(__dirname, './not-there.txt'), cb); 168 | }, function (err, data) { 169 | should.exist(err); 170 | err.should.be.instanceof(Error); 171 | should.not.exist(data); 172 | actualRetryCount.should.equal(0); 173 | done(); 174 | }); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /tests/wait-retry-execute-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var chaiAsPromised = require('chai-as-promised'); 5 | var requestPromise = require('request-promise'); 6 | 7 | chai.use(chaiAsPromised); 8 | chai.should(); 9 | 10 | var polly = require('..'); 11 | 12 | describe('The wait and retry policy with a asynchronous promise call', function () { 13 | 14 | beforeEach(function () { 15 | polly.defaults.delay = 1; 16 | }); 17 | 18 | it('should return the result when no error', function () { 19 | 20 | return polly() 21 | .waitAndRetry() 22 | .executeForPromise(function ({count}) { 23 | return Promise.resolve(42); 24 | }) 25 | .should.eventually.equal(42); 26 | }); 27 | 28 | it('should reject after an error', function () { 29 | 30 | return polly() 31 | .waitAndRetry() 32 | .executeForPromise(function () { 33 | return Promise.reject(new Error("Wrong value")); 34 | }) 35 | .should.eventually 36 | .be.rejectedWith(Error, 'Wrong value'); 37 | }); 38 | 39 | it('should retry once after an error and still fail', function () { 40 | var actualRetryCount = 0; 41 | 42 | return polly() 43 | .waitAndRetry() 44 | .executeForPromise(function ({count}) { 45 | return new Promise(function (resolve, reject) { 46 | actualRetryCount = count; 47 | reject(new Error("Wrong value")); 48 | }); 49 | }) 50 | .should.eventually 51 | .be.rejected 52 | .then(function () { 53 | actualRetryCount.should.equal(1); 54 | }); 55 | }); 56 | 57 | it('should retry five times after an error and still fail', function () { 58 | var actualRetryCount = 0; 59 | 60 | return polly() 61 | .waitAndRetry([1, 1, 1, 1, 1]) 62 | .executeForPromise(function ({count}) { 63 | return new Promise(function (resolve, reject) { 64 | actualRetryCount = count; 65 | reject(new Error("Wrong value")); 66 | }); 67 | }) 68 | .should.eventually 69 | .be.rejected 70 | .then(function () { 71 | actualRetryCount.should.equal(5); 72 | }); 73 | }); 74 | 75 | it('should retry once after an error and succeed', function () { 76 | var actualRetryCount = 0; 77 | 78 | return polly() 79 | .waitAndRetry() 80 | .executeForPromise(function ({count}) { 81 | return new Promise(function (resolve, reject) { 82 | actualRetryCount = count; 83 | if (actualRetryCount < 1) { 84 | reject(new Error("Wrong value")); 85 | } else { 86 | resolve(42); 87 | } 88 | }); 89 | }) 90 | .should.eventually.equal(42) 91 | .then(function () { 92 | actualRetryCount.should.equal(1); 93 | }); 94 | }); 95 | 96 | it('should retry four times specifying delays after an error and succeed', function () { 97 | var actualRetryCount = 0; 98 | 99 | return polly() 100 | .waitAndRetry([1, 1, 1, 1, 1]) 101 | .executeForPromise(function ({count}) { 102 | return new Promise(function (resolve, reject) { 103 | actualRetryCount = count; 104 | if (actualRetryCount < 4) { 105 | reject(new Error("Wrong value")); 106 | } else { 107 | resolve(42); 108 | } 109 | }); 110 | }) 111 | .should.eventually.equal(42) 112 | .then(function () { 113 | actualRetryCount.should.equal(4); 114 | }); 115 | }); 116 | 117 | it('should retry four times specifying the number after an error and succeed', function () { 118 | var actualRetryCount = 0; 119 | 120 | return polly() 121 | .waitAndRetry(5) 122 | .executeForPromise(function ({count}) { 123 | return new Promise(function (resolve, reject) { 124 | actualRetryCount = count; 125 | if (actualRetryCount < 4) { 126 | reject(new Error("Wrong value")); 127 | } else { 128 | resolve(42); 129 | } 130 | }); 131 | }) 132 | .should.eventually.equal(42) 133 | .then(function () { 134 | actualRetryCount.should.equal(4); 135 | }); 136 | }); 137 | 138 | it('we can load html from Google', function () { 139 | var actualRetryCount = 0; 140 | 141 | return polly() 142 | .waitAndRetry() 143 | .executeForPromise(function ({count}) { 144 | actualRetryCount = count; 145 | return requestPromise('http://www.google.com'); 146 | }) 147 | .should.eventually.be.fulfilled 148 | .then(function () { 149 | actualRetryCount.should.equal(0); 150 | }) 151 | }); 152 | 153 | it('we can\'t load html from an invalid URL', function () { 154 | var actualRetryCount = 0; 155 | 156 | return polly() 157 | .waitAndRetry() 158 | .executeForPromise(function ({count}) { 159 | actualRetryCount = count; 160 | return requestPromise('http://www.this-is-no-site.com'); 161 | }) 162 | .should.eventually.be.rejected 163 | .then(function () { 164 | actualRetryCount.should.equal(1); 165 | }) 166 | }); 167 | }); 168 | --------------------------------------------------------------------------------