Skip to content

Commit

Permalink
add more setup options: onError (function), capture (integer)
Browse files Browse the repository at this point in the history
  • Loading branch information
saadtazi committed Jul 23, 2016
1 parent 0f89442 commit 6963a78
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"presets": ["es2015"],
"presets": ["es2015", "stage-2"],
"plugins": ["transform-es2015-modules-umd"]
}
5 changes: 5 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@
"prefer-arrow-callback": "off",
"max-len": "off",
"consistent-return": "off"
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
}
}
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5
6
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: node_js
node_js:
- "6"
- "5"
- "4"
- "0.12"
Expand Down
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ until(aFunction, aConditionFunction, someOptions).then(...);

Supports 3 modes:
* *infinite*: (no `duration` or `retries` option)
* `retries` option (`retries` option): the
* `duration` option:
* `retries` option (`retries` option)
* `duration` option:=

In theory (...and in practice), you can combine `retries` and `duration` options.

To throttle the calls, you can pass `wait` option. The first call is done immediately. Then, if the condition is not satisfied, it waits `wait`ms before executing the function a second time (...and so on...)
To "throttle" the calls, you can pass `wait` option. The first call is done immediately. Then, if the condition is not satisfied, it waits `wait`ms before executing the function a second time (...and so on...)

# Usage

Expand Down Expand Up @@ -44,11 +44,38 @@ until(
});
```

# Setup
# Setup / Reset

This library also expose a `setup({ promise })` function that allows to:
* specify a Promise library like `bluebird` if needed (defaults to `Promise` which is available in nodejs and most [modern browser](http://caniuse.com/#search=promise))
This library also expose a `setup(options)` function that allows to configure default options that will be used eveytime `until` is called (until `setup(options)` or `reset()` are called):
* `Promise`: specify a Promise library like `bluebird` if needed (defaults to `Promise` which is available in nodejs and most [modern browser](http://caniuse.com/#search=promise))
* `onError`: define a custom Error handler: its signature is:
```
// `reject` should be called exactly once.
onError({ errorType, reject, nbAttempts, startedAt, capturedResults }, options)
```
* `captureResults`: if not falsy and > 0, `until` will cature the last X results and pass them to `onError` handler (`capturedResults` property)
* `wait`: time to wait between 2 calls - default: 0
* `duration`: max number of milliseconds before rejecting - default: Infinity
* `retries`: default number of retries before rejecting - default: Infinity


Note that any of those options can also be used when invoking `until(func, testFunc, options)` (third param). Adding those options when invoking `until` will not modify the default `until` options

The default options are:
```
{
wait: 0,
captureResults: 0,
Promise,
onError({ errorType, reject, nbAttempts, startedAt, capturedResults }, options) {
let err = new Error(`condition not satified after ${Date.now() - startedAt}ms / nbAttempts: ${nbAttempts}`);
// note that you can attach properties to error if needed. For example:
err.duration = Date.now() - startedAt;
Object.assign(err, { nbAttempts, errorType, startedAt, capturedResults, options });
reject(err);
}
}
```
*Gotcha*
* When used with `duration` option, we cannot guarantee that it will take exactly or a most `duration`ms, mostly because of [the nature of Javascript time](http://ejohn.org/blog/accuracy-of-javascript-time/). [Additional info here](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#Notes).

Expand Down
118 changes: 77 additions & 41 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports"], factory);
define(['exports'], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
Expand All @@ -11,69 +11,103 @@
global.index = mod.exports;
}
})(this, function (exports) {
"use strict";
'use strict';

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setup = setup;
var Prom = Promise;
exports.reset = reset;

function _toConsumableArray(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
arr2[i] = arr[i];
}

return arr2;
} else {
return Array.from(arr);
}
}

var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];

for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}

return target;
};

var defaultOptions = {
wait: 0,
captureResults: 0,
Promise: Promise,
onError: function onError(_ref, options) {
var errorType = _ref.errorType;
var reject = _ref.reject;
var nbAttempts = _ref.nbAttempts;
var startedAt = _ref.startedAt;
var capturedResults = _ref.capturedResults;

var err = new Error('condition not satified after ' + (Date.now() - startedAt) + 'ms / nbAttempts: ' + nbAttempts);
err.duration = Date.now() - startedAt;
Object.assign(err, { nbAttempts: nbAttempts, errorType: errorType, startedAt: startedAt, capturedResults: capturedResults, options: options });
reject(err);
}
};

var modifiedOptions = _extends({}, defaultOptions);

// setTimeout, the Promise way...
var delay = function delay(duration) {
return new Prom(function (resolve) {
return new modifiedOptions.Promise(function (resolve) {
return setTimeout(function () {
return resolve();
}, duration);
});
};

var pollUntil = function pollUntil(func, conditionFunction) {
var _ref = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];

var _modifiedOptions$opti = _extends({}, modifiedOptions, options);

var _ref$wait = _ref.wait;
var wait = _ref$wait === undefined ? 0 : _ref$wait;
var duration = _ref.duration;
var retries = _ref.retries;
var Promise = _modifiedOptions$opti.Promise;
var wait = _modifiedOptions$opti.wait;
var duration = _modifiedOptions$opti.duration;
var retries = _modifiedOptions$opti.retries;
var onError = _modifiedOptions$opti.onError;
var _modifiedOptions$opti2 = _modifiedOptions$opti.captureResults;
var captureResults = _modifiedOptions$opti2 === undefined ? 0 : _modifiedOptions$opti2;

var nbAttempts = 0;
var startedAt = Date.now();
var alreadyFailed = false;

var hasExpired = function hasExpired() {
return duration && startedAt + duration < Date.now();
};

var hasTriedEnough = function hasTriedEnough() {
if (retries) {
return nbAttempts >= retries;
}
return false;
};

return new Prom(function (resolve, reject) {
return new Promise(function (resolve, reject) {
// to fail "exactly" after `duration`,
// we have to reject in a `setInterval()`
var timeout = void 0;

var fail = function fail(msg) {
if (alreadyFailed) {
return;
}
alreadyFailed = true;
// time it took to fail
// mostly for testing and validation
var err = new Error(msg);
err.duration = Date.now() - startedAt;
err.nbAttempts = nbAttempts;

reject(err);
};
var capturedResults = [];

if (duration) {
timeout = setTimeout(function () {
if (!alreadyFailed) {
fail("condition not satified after " + (Date.now() - startedAt) + "ms");
alreadyFailed = true;
return onError({ errorType: 'duration', reject: reject, nbAttempts: nbAttempts, startedAt: startedAt, capturedResults: capturedResults }, options);
}
}, duration);
}
Expand All @@ -83,7 +117,10 @@
var executeAndCheckCondition = function executeAndCheckCondition() {
nbAttempts++;
// just in case `func` does not return a promise...
return Prom.resolve(func()).then(function (res) {
return Promise.resolve(func()).then(function (res) {
if (captureResults) {
capturedResults = [].concat(_toConsumableArray(capturedResults.slice(1 - captureResults)), [res]);
}
if (conditionFunction(res)) {
// success
if (timeout) {
Expand All @@ -92,15 +129,11 @@
return resolve(res);
}
if (hasTriedEnough()) {
return fail("condition not satified after " + retries + " attempts");
alreadyFailed = true;
return onError({ errorType: 'retries', reject: reject, nbAttempts: nbAttempts, startedAt: startedAt, capturedResults: capturedResults }, options);
}
delay(wait).then(function () {
if (!alreadyFailed) {
// there is no guarantee that setTimeout() will run
// when it is suppose to run... So we make sure...
if (hasExpired()) {
return fail("condition not satified after " + (Date.now() - startedAt) + "ms");
}
executeAndCheckCondition();
}
});
Expand All @@ -112,12 +145,15 @@
};

function setup() {
var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

var _ref2$promise = _ref2.promise;
var promise = _ref2$promise === undefined ? Promise : _ref2$promise;
modifiedOptions = _extends({}, modifiedOptions, options);
return modifiedOptions;
}

Prom = promise;
function reset() {
modifiedOptions = _extends({}, defaultOptions);
return modifiedOptions;
}

exports.default = pollUntil;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "until-promise",
"version": "0.2.2",
"version": "0.3.0",
"description": "utility functions that poll until a condition is met",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -32,7 +32,7 @@
"devDependencies": {
"babel-cli": "^6.7.5",
"babel-plugin-transform-es2015-modules-umd": "^6.6.5",
"babel-preset-es2015": "^6.6.0",
"babel-preset-stage-2": "^6.11.0",
"babel-register": "^6.7.2",
"bluebird": "^3.3.4",
"chai": "^3.5.0",
Expand Down

0 comments on commit 6963a78

Please sign in to comment.