├── .gitignore ├── .travis.yml ├── package.json ├── test ├── error.js ├── basic.js └── algorithm.js ├── oibackoff.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *~ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oibackoff", 3 | "description": "Incremental backoff flow-control for any : fn(function(err, data) { ... });", 4 | "version": "1.0.1", 5 | "author": { 6 | "name": "Andrew Chilton", 7 | "email": "andychilton@gmail.com", 8 | "url": "http://chilts.org/" 9 | }, 10 | "homepage": "https://github.com/chilts/oibackoff", 11 | "contributors": [{ 12 | "name": "Andrew Chilton", 13 | "email": "andychilton@gmail.com", 14 | "web": "http://chilts.org/" 15 | },{ 16 | "name": "Daniel Stevens", 17 | "email": "daniel.stevens@senico.com", 18 | "web": "http://senico.com/" 19 | }], 20 | "devDependencies": { 21 | "tap": ">= 0.2.5" 22 | }, 23 | "dependencies": { 24 | "underscore": "~1.3.3" 25 | }, 26 | "main": "oibackoff.js", 27 | "engines": { 28 | "node": ">= 0.6.0" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/chilts/oibackoff.git" 33 | }, 34 | "bugs": { 35 | "url": "http://github.com/chilts/oibackoff/issues" 36 | }, 37 | "licenses": [ 38 | { 39 | "type": "MIT", 40 | "url": "http://opensource.org/licenses/MIT" 41 | } 42 | ], 43 | "keywords": [ 44 | "function", 45 | "backoff", 46 | "incremental", 47 | "exponentail", 48 | "linear", 49 | "control flow", 50 | "flow" 51 | ], 52 | "scripts": { 53 | "test": "tap test/" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/error.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // error.js - tests for errors for oibackoff 4 | // 5 | // Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/ 6 | // Written by Andrew Chilton 7 | // 8 | // License: http://opensource.org/licenses/MIT 9 | // 10 | // -------------------------------------------------------------------------------------------------------------------- 11 | 12 | // -------------------------------------------------------------------------------------------------------------------- 13 | // requires 14 | 15 | // core 16 | var fs = require('fs'); 17 | 18 | // modules 19 | var _ = require('underscore'); 20 | var tap = require("tap"); 21 | var test = tap.test; 22 | var backoff = require('../').backoff(); 23 | 24 | // -------------------------------------------------------------------------------------------------------------------- 25 | 26 | test("Check the number of args (should be at least two)", function (t) { 27 | t.plan(3); 28 | 29 | try { 30 | backoff(function() {}); 31 | } 32 | catch (e) { 33 | t.ok(e, 'backoff() threw an error as expected (only 1 arg)'); 34 | } 35 | 36 | try { 37 | backoff(); 38 | } 39 | catch (e) { 40 | t.ok(e, 'backoff() threw an error as expected (no args)'); 41 | } 42 | 43 | backoff(function(callback) { callback(null, 1); }, function(err, data) { 44 | t.equal(data, 1, 'backoff was fine (as suspected)'); 45 | }); 46 | 47 | t.end(); 48 | }); 49 | 50 | test("Check default max tries", function (t) { 51 | t.plan(3); 52 | 53 | // always return failure from the function 54 | var fn = function(callback) { 55 | callback('Error', null); 56 | }; 57 | 58 | backoff(fn, function(err, data, priorErrors) { 59 | t.equal(err, 'Error', 'error received'); 60 | t.equal(data, null, 'no data received'); 61 | t.equal(priorErrors.length, 2, 'There were 2 errors prior to this one'); 62 | t.end(); 63 | }); 64 | }); 65 | 66 | // -------------------------------------------------------------------------------------------------------------------- 67 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // basic.js - basic tests for oibackoff 4 | // 5 | // Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/ 6 | // Written by Andrew Chilton 7 | // 8 | // License: http://opensource.org/licenses/MIT 9 | // 10 | // -------------------------------------------------------------------------------------------------------------------- 11 | 12 | // -------------------------------------------------------------------------------------------------------------------- 13 | // requires 14 | 15 | // core 16 | var fs = require('fs'); 17 | 18 | // modules 19 | var _ = require('underscore'); 20 | var tap = require("tap"); 21 | var test = tap.test; 22 | var backoff = require('../').backoff(); 23 | 24 | // -------------------------------------------------------------------------------------------------------------------- 25 | 26 | test("Basic successful callback", function (t) { 27 | // do a basic fs.stat of this __filename 28 | 29 | t.plan(3); 30 | 31 | backoff(fs.stat, __filename, function(err, stats, priorErrors) { 32 | t.equal(err, null, 'Error should be null'); 33 | t.ok(stats.size, 'stats.size should be populated (with a number)'); 34 | t.equal(priorErrors.length, 0, 'There should be no prior errors'); 35 | t.end(); 36 | }); 37 | 38 | }); 39 | 40 | var count = 0; 41 | function statWithOneFail(filename, callback) { 42 | if ( count === 0 ) { 43 | count++; 44 | callback('Error', null); 45 | return; 46 | } 47 | else { 48 | count = 0; 49 | } 50 | // now call the real one 51 | fs.stat(filename, callback); 52 | } 53 | 54 | test("Basic successful callback", function (t) { 55 | // call a modified fs.stat which returns a fail on the first go 56 | t.plan(3); 57 | 58 | backoff(statWithOneFail, __filename, function(err, stats, priorErrors) { 59 | t.equal(err, null, 'Error should be null'); 60 | t.ok(stats.size, 'stats.size should be populated (with a number)'); 61 | t.equal(priorErrors.length, 1, 'There should be one prior errors'); 62 | t.end(); 63 | }); 64 | 65 | }); 66 | 67 | test("Basic intermediate callback", function (t) { 68 | t.plan(5); 69 | 70 | // call a modified fs.stat which returns a fail on the first go 71 | var intermediate = function (err, tries, delay) { 72 | t.equal(err, 'Error', 'err should be \'Error\''); 73 | t.equal(tries, 1, 'tries count should be 1'); 74 | t.ok(delay, "delay should be populated (with a number)"); 75 | return false; 76 | }; 77 | 78 | backoff(statWithOneFail, __filename, intermediate, function(err, stats, priorErrors) { 79 | t.equal(err, 'Error', 'err should be \'Error\''); 80 | t.equal(priorErrors.length, 0, 'There should be no prior errors'); 81 | t.end(); 82 | }); 83 | 84 | }); 85 | 86 | // -------------------------------------------------------------------------------------------------------------------- 87 | -------------------------------------------------------------------------------- /oibackoff.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // oibackoff.js - backoff functionality for any : fn(function(err, data) { ... }); 4 | // 5 | // Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/ 6 | // Written by Andrew Chilton 7 | // 8 | // License: http://opensource.org/licenses/MIT 9 | // 10 | // -------------------------------------------------------------------------------------------------------------------- 11 | 12 | var _ = require('underscore'); 13 | 14 | var defaults = { 15 | 'maxTries' : 3, 16 | 'algorithm' : 'exponential', 17 | 'delayRatio' : 1, // you could make it any other integer or fraction (e.g. 0.25) 18 | }; 19 | 20 | // returns 1, 2, 3, 4, 5, 6, 7, ... 21 | function incremental(n) { 22 | return n + 1; 23 | } 24 | 25 | // memoizes 0, 1, 2, 4, 8, 16, 32, ... 26 | var exp = []; 27 | function exponential(n) { 28 | if ( exp[n] ) { 29 | return exp[n]; 30 | } 31 | if ( n === 0 ) { 32 | exp[0] = 1; 33 | return exp[0]; 34 | } 35 | if ( n === 1 ) { 36 | exp[1] = 2; 37 | return exp[1]; 38 | } 39 | exp[n] = exponential(n-1) * 2; 40 | return exp[n]; 41 | } 42 | 43 | // memoizes 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... 44 | var fib = []; 45 | function fibonacci(n) { 46 | if ( fib[n] ) { 47 | return fib[n]; 48 | } 49 | if ( n <= 1 ) { 50 | fib[n] = n; 51 | return fib[n]; 52 | } 53 | fib[n] = fibonacci(n-2) + fibonacci(n-1); 54 | return fib[n]; 55 | } 56 | 57 | var algorithm = { 58 | fibonacci : fibonacci, 59 | exponential : exponential, 60 | incremental : incremental, 61 | }; 62 | 63 | var backoff = function(opts) { 64 | 65 | // build up all the options 66 | opts = _.extend({}, defaults, opts); 67 | 68 | return function() { 69 | if ( arguments.length < 2 ) { 70 | throw 'Provide at least two args : backoff(fn, ..., callback)'; 71 | } 72 | 73 | // set up a few things we need to keep a check of 74 | var tries = 0; 75 | 76 | // the function to call is the first arg, the last is the callback 77 | // an intermediate function can be called on error if provided 78 | var args = Array.prototype.slice.call(arguments); 79 | var fn = args.shift(); 80 | var callback = args.pop(); 81 | var intermediate = function() {}; 82 | if ( _.isFunction( _.last( args ) ) ) { 83 | intermediate = args.pop(); 84 | } 85 | 86 | var priorErrors = []; 87 | 88 | // create the function we want to call when fn() calls back 89 | var myCallback = function(err, data) { 90 | if ( err ) { 91 | // figure out the actual delay using the algorithm, the retry count and the delayRatio 92 | var delay = algorithm[opts.algorithm](tries) * opts.delayRatio; 93 | 94 | // call this again but only if we 95 | var doAgain = false; 96 | if ( intermediate(err, tries, delay) === false ) { 97 | // intermediate function has told us not to try again 98 | callback(err, null, priorErrors); 99 | return; 100 | } 101 | if ( opts.maxTries === 0 ) { 102 | doAgain = true; 103 | } 104 | else { 105 | if ( tries < opts.maxTries ) { 106 | doAgain = true; 107 | } 108 | else { 109 | // we've retried enough, call callback as a failure 110 | callback(err, null, priorErrors); 111 | return; 112 | } 113 | } 114 | 115 | // remember this error 116 | priorErrors.push(err); 117 | 118 | if ( doAgain ) { 119 | // ... and check it isn't over maxDelay 120 | if ( opts.maxDelay && delay > opts.maxDelay ) { 121 | delay = opts.maxDelay; 122 | } 123 | 124 | setTimeout(function() { 125 | // increment how many tries we have done 126 | tries++; 127 | 128 | // now call it again 129 | fn.apply(null, args); 130 | }, delay * 1000); 131 | } 132 | return; 133 | } 134 | callback(null, data, priorErrors); 135 | }; 136 | 137 | // add our own callback to the args and call the incoming function 138 | args.push(myCallback); 139 | tries++; 140 | fn.apply(null, args); 141 | }; 142 | }; 143 | 144 | // -------------------------------------------------------------------------------------------------------------------- 145 | // exports 146 | 147 | exports.exponential = exponential; 148 | exports.incremental = incremental; 149 | exports.fibonacci = fibonacci; 150 | exports.backoff = backoff; 151 | 152 | // -------------------------------------------------------------------------------------------------------------------- 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oibackoff 2 | 3 | Incremental backoff flow-control for any `fn(function(err, data) { ... });`. 4 | 5 | [![Build Status](https://secure.travis-ci.org/chilts/oibackoff.png?branch=master)](http://travis-ci.org/chilts/oibackoff) 6 | 7 | ## Stable ## 8 | 9 | Please note that this repo is stable. This means there may not be many commits, issues or releases. This is 10 | normal. Please submit any issues for any queries you may have. 11 | 12 | ## Features ## 13 | 14 | * three different backoff algorithms: 'exponential', 'fibonacci' and 'incremental' 15 | * max number of tries 16 | * max time to wait for any retry 17 | * scaling of the delay between tries 18 | 19 | Your code can stay the same plus you also get extra information about intermediate errors. 20 | 21 | ## Examples ## 22 | 23 | Original code: 24 | 25 | ```js 26 | var dns = require('dns'); 27 | 28 | // original code 29 | dns.resolve('chilts.org', function(err, addresses) { 30 | if (err) { 31 | // do something to recover from this error 32 | return; 33 | } 34 | 35 | // do something with addresses 36 | console.log(addresses); 37 | }); 38 | ``` 39 | 40 | Using exponential backoff, with a maxium of 5 tries, with delays of 0.2, 0.4, 0.8, 1.6 and 3.2 seconds is fairly 41 | similar and you can reuse the 'backoff' function many times: 42 | 43 | ```js 44 | var backoff = require('oibackoff').backoff({ 45 | algorithm : 'exponential', 46 | delayRatio : 0.2, 47 | maxTries : 5, 48 | }); 49 | 50 | backoff(dns.resolve, 'chilts.org', function(err, addresses, priorErrors) { 51 | if (err) { 52 | // do something to recover from this error 53 | return; 54 | } 55 | 56 | // do something with addresses 57 | console.log(addresses); 58 | }); 59 | ``` 60 | 61 | You can also provide an intermediate function which is called after each error. This method can be useful for logging 62 | or other operations between errors. By returning `false` you can cancel any additional tries. 63 | 64 | ```js 65 | var intermediate = function(err, tries, delay) { 66 | console.log(err); // last error 67 | console.log(tries); // total number of tries performed thus far 68 | console.log(delay); // the delay for the next attempt 69 | return false; // this will cancel additional tries 70 | }; 71 | 72 | backoff(dns.resolve, 'chilts.org', intermediate, callback); 73 | ``` 74 | 75 | Notes: 76 | 77 | * 'err' contains the last error encountered (if maxTries was reached without success) 78 | * 'addresses' contrains the same as the original upon success, or null if all attempts failed 79 | * 'priorErrors' is informational and you may ignore it or use it to help you diagnose problems 80 | 81 | ## Options ## 82 | 83 | ### maxTries ### 84 | 85 | Default: 3 86 | 87 | Will retry a maximum number of times. If you don't want a maxiumum, set this to 0. 88 | 89 | ### delayRatio ### 90 | 91 | Default : 1 92 | 93 | This is the ratio for each delay between each try (in seconds). 94 | 95 | If you choose the exponential algorithm, then 1s delayRatio will result in delays of 1, 2, 4, 8 etc 96 | 97 | ### algorithm ### 98 | 99 | Default : `exponential` 100 | 101 | Valid Values : `exponential`, `fibonacci`, `incremental` ;) 102 | 103 | ### maxDelay ### 104 | 105 | No Default. 106 | 107 | If your chosen backoff strategy reaches a point which is above this number, then each succesive retry will top-out at 108 | 'maxDelay' e.g. if you choose 'exponential', with a delayRatio of 1 and maxTries at 10, the retry delays will be 1, 2, 109 | 4, 8, 10, 10, ... (instead of 1, 2, 4, 8, 16, 32, ...). 110 | 111 | ## Example Backoff Stategies ## 112 | 113 | ```js 114 | var oibackoff = require('oibackoff'); 115 | 116 | // 0.4, 0.8, 1.6, 3.2, 6.4, ... 117 | var backoff = oibackoff.backoff({ 118 | algorithm : 'exponential', 119 | delayRatio : 0.4, 120 | }); 121 | 122 | // 1, 2, 3, 4, 5, ... 123 | var backoff = oibackoff.backoff({ 124 | algorithm : 'incremental', 125 | delayRatio : 1, 126 | }); 127 | 128 | // 0.5, 0.5, 1.0, 1.5, 2.5, 4, ... 129 | var backoff = oibackoff.backoff({ 130 | algorithm : 'fibonacci', 131 | delayRatio : 0.5, 132 | }); 133 | ``` 134 | 135 | ## Author ## 136 | 137 | Written by: [Andrew Chilton](http://chilts.org/) - [Twitter](https://twitter.com/andychilton). 138 | 139 | Contributors: 140 | [Daniel Stevens - Senico](http://senico.com/) 141 | 142 | ## License ## 143 | 144 | The MIT License : http://opensource.org/licenses/MIT 145 | 146 | Copyright (c) 2011-2012 AppsAttic Ltd. http://appsattic.com/ 147 | 148 | Copyright (c) 2013-2016 Andrew Chilton. http://chilts.org/ 149 | 150 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 151 | documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the 152 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 153 | persons to whom the Software is furnished to do so, subject to the following conditions: 154 | 155 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 156 | Software. 157 | 158 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 159 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 160 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 161 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 162 | 163 | (Ends) 164 | -------------------------------------------------------------------------------- /test/algorithm.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // algorithm.js - tests for the different algorithms for oibackoff 4 | // 5 | // Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/ 6 | // Written by Andrew Chilton 7 | // 8 | // License: http://opensource.org/licenses/MIT 9 | // 10 | // -------------------------------------------------------------------------------------------------------------------- 11 | 12 | // -------------------------------------------------------------------------------------------------------------------- 13 | // requires 14 | 15 | // modules 16 | var _ = require('underscore'); 17 | var tap = require("tap"); 18 | var test = tap.test; 19 | var oibackoff = require('../'); 20 | 21 | // creates a function which will fail n times before succeeding 22 | function failNTimes(n) { 23 | var count = 0; 24 | return function(callback) { 25 | count++; 26 | if ( count <= n ) { 27 | callback('Error', null); 28 | return; 29 | } 30 | callback(null, 'Ok'); 31 | }; 32 | } 33 | 34 | // -------------------------------------------------------------------------------------------------------------------- 35 | 36 | test('Test our algorithms', function(t) { 37 | t.plan(18); 38 | 39 | t.equal(oibackoff.incremental(0), 1, 'Incremental(0)'); 40 | t.equal(oibackoff.incremental(1), 2, 'Incremental(1)'); 41 | t.equal(oibackoff.incremental(2), 3, 'Incremental(2)'); 42 | t.equal(oibackoff.incremental(3), 4, 'Incremental(3)'); 43 | t.equal(oibackoff.incremental(4), 5, 'Incremental(4)'); 44 | t.equal(oibackoff.incremental(5), 6, 'Incremental(5)'); 45 | 46 | t.equal(oibackoff.exponential(0), 1, 'Exponential(0)'); 47 | t.equal(oibackoff.exponential(1), 2, 'Exponential(1)'); 48 | t.equal(oibackoff.exponential(2), 4, 'Exponential(2)'); 49 | t.equal(oibackoff.exponential(3), 8, 'Exponential(3)'); 50 | t.equal(oibackoff.exponential(4), 16, 'Exponential(4)'); 51 | t.equal(oibackoff.exponential(5), 32, 'Exponential(5)'); 52 | 53 | t.equal(oibackoff.fibonacci(0), 0, 'Fibonacci(0)'); 54 | t.equal(oibackoff.fibonacci(1), 1, 'Fibonacci(1)'); 55 | t.equal(oibackoff.fibonacci(2), 1, 'Fibonacci(2)'); 56 | t.equal(oibackoff.fibonacci(3), 2, 'Fibonacci(3)'); 57 | t.equal(oibackoff.fibonacci(4), 3, 'Fibonacci(4)'); 58 | t.equal(oibackoff.fibonacci(5), 5, 'Fibonacci(5)'); 59 | 60 | t.end(); 61 | }); 62 | 63 | test("Check that 'incremental' backoff works", function (t) { 64 | t.plan(3); 65 | 66 | var backoff = oibackoff.backoff({ 67 | algorithm : 'incremental', 68 | maxTries : 10, // so we don't hit the limit 69 | delayRatio : 0.25, 70 | }); 71 | 72 | backoff(failNTimes(3), function(err, data, priorErrors) { 73 | t.equal(err, null, 'Incremental: function did not fail'); 74 | t.equal(data, 'Ok', 'Incremental: function returned a good result'); 75 | t.equal(priorErrors.length, 3, 'Incremental: function failed thrice times before succeeding'); 76 | t.end(); 77 | }); 78 | }); 79 | 80 | test("Check that 'exponential' backoff works", function (t) { 81 | t.plan(3); 82 | 83 | var backoff = oibackoff.backoff({ 84 | algorithm : 'exponential', 85 | maxTries : 10, // so we don't hit the limit 86 | delayRatio : 0.25, 87 | }); 88 | 89 | backoff(failNTimes(3), function(err, data, priorErrors) { 90 | t.equal(err, null, 'Exponential: function did not fail'); 91 | t.equal(data, 'Ok', 'Exponential: function returned a good result'); 92 | t.equal(priorErrors.length, 3, 'Exponential: function failed three times before succeeding'); 93 | t.end(); 94 | }); 95 | }); 96 | 97 | test("Check that 'fibonacci' backoff works", function (t) { 98 | t.plan(3); 99 | 100 | var backoff = oibackoff.backoff({ 101 | algorithm : 'fibonacci', 102 | maxTries : 10, // so we don't hit the limit 103 | delayRatio : 0.25, 104 | }); 105 | 106 | backoff(failNTimes(3), function(err, data, priorErrors) { 107 | t.equal(err, null, 'Fibonacci: function did not fail'); 108 | t.equal(data, 'Ok', 'Fibonacci: function returned a good result'); 109 | t.equal(priorErrors.length, 3, 'Fibonacci: function failed three times before succeeding'); 110 | t.end(); 111 | }); 112 | }); 113 | 114 | test("No Max Tries", function (t) { 115 | t.plan(3); 116 | 117 | // go up in tenths of a second 118 | var backoff = oibackoff.backoff({ 119 | algorithm : 'incremental', 120 | delayRatio : 0.1, 121 | maxTries : 0, 122 | }); 123 | 124 | backoff(failNTimes(8), function(err, data, priorErrors) { 125 | t.equal(err, null, 'NoMaxTries: function did not fail'); 126 | t.equal(data, 'Ok', 'NoMaxTries: function returned a good result'); 127 | t.equal(priorErrors.length, 8, 'NoMaxTries: function failed eight times before succeeding'); 128 | t.end(); 129 | }); 130 | }); 131 | 132 | test("With a Max Tries (never succeeds)", function (t) { 133 | t.plan(3); 134 | 135 | // go up in tenths of a second 136 | var backoff = oibackoff.backoff({ 137 | algorithm : 'incremental', 138 | delayRatio : 0.1, 139 | maxTries : 3, 140 | }); 141 | 142 | var alwaysFail = function(callback) { 143 | callback('Error', null); 144 | }; 145 | backoff(alwaysFail, function(err, data, priorErrors) { 146 | t.equal(err, 'Error', "MaxTries=3: Function's last fail was 'Error'"); 147 | t.equal(data, null, 'MaxTries=3: function failed overall'); 148 | t.equal(priorErrors.length, 2, 'MaxTries=3: function failed twice prior to this one'); 149 | t.end(); 150 | }); 151 | }); 152 | 153 | // -------------------------------------------------------------------------------------------------------------------- 154 | --------------------------------------------------------------------------------