async-deco
Advanced tools
Comparing version 2.0.1 to 3.0.0
{ | ||
"name": "async-deco", | ||
"version": "2.0.1", | ||
"description": "A collection of decorators you can use to wrap functions (callback or promise based) to make them more robust", | ||
"version": "3.0.0", | ||
"description": "A collection of decorators for adding features to asynchronous functions (callback or promise based).", | ||
"main": "index.js", | ||
@@ -6,0 +6,0 @@ "scripts": { |
130
README.md
@@ -5,9 +5,9 @@ async-deco | ||
This is a collection of function decorators. It allows to timeout, retry, throttle, limit and much more! | ||
async-deco is a collection of decorators. It allows to add features such as timeout, retry, dedupe, limit and much more! | ||
They can be combined together using the "compose" function (included). | ||
Callback and promises | ||
===================== | ||
All decorators are designed to work with functions using a callback or returning a promise. In case of callbacks, it must follow the [node convention](https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions): the callback should be the last argument and its arguments should be, an error instance and the output of the function. | ||
Callback and promises | ||
===================== | ||
Every decorator is available in two different flavours: | ||
@@ -20,3 +20,3 @@ * callback based: | ||
```js | ||
logDecorator(logger)(function (a, b, c, next) { | ||
var decoratedFunction = logDecorator(logger)(function (a, b, c, next) { | ||
... | ||
@@ -32,3 +32,3 @@ next(undefined, result); // or next(error); | ||
```js | ||
logDecorator(logger)(function (a, b, c) { | ||
var decoratedFunction = logDecorator(logger)(function (a, b, c) { | ||
return new Promise(function (resolve, reject) { | ||
@@ -40,2 +40,3 @@ ... | ||
``` | ||
Then you can run the decorated function. | ||
@@ -61,3 +62,3 @@ Requiring the library | ||
---------------- | ||
You can pass a logger to the decorators. This function is called with the same arguments of the original function and should return a function with this signature: | ||
You can pass a logger to any decorators. This function is called with the same arguments of the original function and should return a function with this signature: | ||
```js | ||
@@ -67,3 +68,3 @@ function (type, obj) | ||
* Type is the type of event to log | ||
* obj contains useful informations, depending on the type | ||
* obj contains useful informations, depending on the type of the decorator | ||
@@ -85,3 +86,3 @@ So for example, assuming that the first argument of the decorated function is a name: | ||
------- | ||
It allows to remember the last results | ||
It caches the result. At any subsequent calls it will return the cached result. | ||
```js | ||
@@ -91,6 +92,6 @@ var memoizeDecorator = require('async-deco/callback/memoize'); | ||
var simpleMemoize = memoizeDecorator(getKey, logger); | ||
simpleMemoize(function (..., cb) { .... }); | ||
var myfunc = simpleMemoize(function (..., cb) { .... }); | ||
``` | ||
It takes 2 arguments: | ||
* a getKey function [optional]: when it runs against the original arguments it returns the key used for the caching. If it is missing the function memoize the first result and returns always the same. | ||
* a getKey function [optional]: it runs against the original arguments and returns the key used for the caching. If it is missing, only one result will be memoized. | ||
* a logger function (logs "cachehit") [optional] | ||
@@ -100,3 +101,3 @@ | ||
----- | ||
It is a more sophisticated version of the memoize decorator. It can be used to for caching in a db/file etc (You may have to write your own cache object). | ||
It is a more sophisticated version of the memoize decorator. It can be used for caching in a db/file etc (You may have to write your own cache object). | ||
memoize-cache is an in-memory reference implementation (https://github.com/sithmel/memoize-cache). | ||
@@ -107,3 +108,3 @@ ```js | ||
var cached = cacheDecorator(cache, logger); | ||
cached(function (..., cb) { .... }); | ||
var myfunc = cached(function (..., cb) { .... }); | ||
``` | ||
@@ -120,9 +121,9 @@ It takes 2 arguments: | ||
var fallback = fallbackDecorator(function (err, a, b, c, func) { | ||
var fallback = fallbackDecorator(function (a, b, c, func) { | ||
func(undefined, 'giving up'); | ||
}, Error, logger); | ||
fallback(function (..., cb) { .... }); | ||
var myfunc = fallback(function (..., cb) { .... }); | ||
``` | ||
It takes 3 arguments: | ||
* fallback function [mandatory]. It takes the err, and the original arguments. | ||
* fallback function [mandatory]. It takes the same arguments of the original function (and a callback, even in the promise case). | ||
* error instance for deciding to fallback, or a function taking error and result (if it returns true it'll trigger the fallback) [optional, it falls back on any error by default] | ||
@@ -138,3 +139,3 @@ * logger function (logs "fallback") [optional] | ||
var fallback = fallbackValueDecorator('giving up', Error, logger); | ||
fallback(function (..., cb) { .... }); | ||
var myfunc = fallback(function (..., cb) { .... }); | ||
``` | ||
@@ -148,3 +149,3 @@ It takes 3 arguments: | ||
-------------- | ||
If a function fails, it tries to use a previous cached result | ||
If a function fails, it tries to use a previous cached result. | ||
```js | ||
@@ -154,7 +155,7 @@ var fallbackCacheDecorator = require('async-deco/callback/fallback-cache'); | ||
var fallback = fallbackCacheDecorator(cache, Error, logger); | ||
fallback(function (..., cb) { .... }); | ||
var myfunc = fallback(function (..., cb) { .... }); | ||
``` | ||
It takes 3 arguments: | ||
* a cache object [mandatory]. The interface should be compatible with memoize-cache (https://github.com/sithmel/memoize-cache) | ||
* error instance for deciding to fallback, or a function taking error and result (if it returns true it'll trigger the fallback) [optional, it falls back on any error by default] | ||
* error instance for deciding to fallback, or a function taking the error and result (if it returns true it'll trigger the fallback) [optional, it falls back on any error by default] | ||
* logger function (logs "fallback-cache") [optional] | ||
@@ -169,3 +170,3 @@ | ||
var addLogs = logDecorator(logger); | ||
addLogs(function (..., cb) { .... }); | ||
var myfunc = addLogs(function (..., cb) { .... }); | ||
``` | ||
@@ -175,3 +176,3 @@ | ||
------- | ||
If a function takes to much, returns a timeout exception | ||
If a function takes to much, returns a timeout exception. | ||
```js | ||
@@ -181,3 +182,3 @@ var timeoutDecorator = require('async-deco/callback/timeout'); | ||
var timeout20 = timeoutDecorator(20, logger); | ||
timeout20(function (..., cb) { .... }); | ||
var myfunc = timeout20(function (..., cb) { .... }); | ||
``` | ||
@@ -196,3 +197,3 @@ This will wait 20 ms before returning a TimeoutError. | ||
var retryTenTimes = retryDecorator(10, 0, Error, logger); | ||
retryTenTimes(function (..., cb) { .... }); | ||
var myfunc = retryTenTimes(function (..., cb) { .... }); | ||
``` | ||
@@ -207,42 +208,29 @@ You can initialise the decorator with 3 arguments: | ||
----- | ||
Limit the parallel execution of a function. | ||
Limit the concurrency of a function. | ||
```js | ||
var limitDecorator = require('async-deco/callback/limit'); | ||
var limitToTwo = limitDecorator(2, logger); | ||
limitToTwo(function (..., cb) { .... }); | ||
var limitToTwo = limitDecorator(2, getKey,logger); | ||
var myfunc = limitToTwo(function (..., cb) { .... }); | ||
``` | ||
You can initialise the decorator with 2 arguments: | ||
* number of parallel execution [mandatory] | ||
* a getKey function [optional]: it runs against the original arguments and returns the key used for creating different queues of execution. If it is missing there will be only one execution queue. | ||
* logger function (logs "limit" when a function gets queued) [optional] | ||
Throttle | ||
-------- | ||
It throttles or debounces the execution of a function. The callbacks returns normally with the result. Internally it uses the "getKey" function to group the callbacks into queues. It then executes the debounced (or throttled) function. When it returns a value it will run all the callbacks of the same queue. | ||
Dedupe | ||
------ | ||
It throttles the execution of the function. After collecting the output it dispatches it to all callbacks. | ||
It may use the "getKey" function to group the callbacks into queues. | ||
```js | ||
var debounceDecorator = require('async-deco/callback/debounce'); | ||
var dedupeDecorator = require('async-deco/callback/dedupe'); | ||
var debounce = debounceDecorator(100, 'debounce', undefined, undefined, getLogger); | ||
debounce(function (..., cb) { .... }); | ||
var dedupe = dedupeDecorator(100, getKey, getLogger); | ||
var myfunc = dedupe(function (..., cb) { .... }); | ||
``` | ||
The arguments: | ||
* delay [optional, default 0] the delay before the execution of the function (for debounce) or the number of milliseconds to throttle invocations to. | ||
* timingFunctionName [optional, default "throttle"] it can be the string "throttle" or "debounce" | ||
* options [optional] see below | ||
* getKey | ||
* delay [optional, default 0] the number of milliseconds to throttle invocations to. | ||
* getKey function [optional]: it runs against the original arguments and returns the key used for creating different queues of execution. If it is missing there will be only one execution queue. | ||
* logger function (logs "deduping" whenever is calling more than one callback with the same results) | ||
The options change meaning if they are related to the "throttle" or the "debounce": | ||
For the debounce: | ||
* leading [default false] (boolean): Specify invoking on the leading edge of the timeout. | ||
* maxWait (number): The maximum time func is allowed to be delayed before it’s invoked. | ||
* trailing [default true] (boolean): Specify invoking on the trailing edge of the timeout. | ||
For the throttle: | ||
* leading [default true] (boolean): Specify invoking on the leading edge of the timeout. | ||
* trailing [default true] (boolean): Specify invoking on the trailing edge of the timeout. | ||
For better understanding of throttle/debounce I suggest to read the "lodash" documentation and this article: https://css-tricks.com/the-difference-between-throttling-and-debouncing/. | ||
Utilities | ||
@@ -281,3 +269,3 @@ ========= | ||
------- | ||
It can combine more than one decorators. You can pass either an array or using multiple arguments. "undefined" functions are ignored. | ||
It can combine more than one decorators. You can pass either an array or using multiple arguments. "undefined" items are ignored. | ||
```js | ||
@@ -290,5 +278,45 @@ var compose = require('async-deco/utils/compose'); | ||
decorator(function (..., cb) { .... }); | ||
var newfunc = decorator(function (..., cb) { .... }); | ||
``` | ||
Timeout after 20 ms and then retry 10 times before giving up. | ||
You should consider the last function is the one happen first! | ||
Examples and use cases | ||
====================== | ||
Smart memoize | ||
------------- | ||
Using memoize on an asynchronous function has a conceptual flaw. Let's say for example I have a function with 100ms latency. I call this function every 10 ms. I memoize for 100ms. | ||
``` | ||
executed ⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇ | ||
------------------------------ | ||
requested ⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆ | ||
``` | ||
What happen is that while I am still waiting for the first result (to memoize) I regularly execute other 9 functions. | ||
What if I compose memoize with dedupe? | ||
```js | ||
var decorator = compose( | ||
memoizeDecorator(), | ||
dedupeDecorator(100)); | ||
var newfunc = decorator(function (..., cb) { .... }); | ||
``` | ||
dedupe should fill the gap: | ||
``` | ||
executed ⬇ | ||
------------------------------ | ||
requested ⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆ | ||
``` | ||
Reliable function | ||
----------------- | ||
Imagine a case in which you want to be sure you did everything to get a result, and in case is not possible you want to return a sane fallback: | ||
```js | ||
var decorator = compose( | ||
fallbackDecorator(getFallbackResult), // last resort fallback | ||
fallbackCacheDecorator(cache), // try to use a previous cached output | ||
retryDecorator(3), // it retry 3 times | ||
timeoutDecorator(5000)); // it times out after 5 seconds | ||
var newfunc = decorator(function (..., cb) { .... }); | ||
``` |
@@ -23,3 +23,3 @@ var noopLogger = require('./noop-logger'); | ||
logger('fallback', {actualResult: {err: err, res: dep}}); | ||
fallbackFunction.apply(context, [err].concat(args.slice(0, -1), cb)); | ||
fallbackFunction.apply(context, args.slice(0, -1).concat(cb)); | ||
} | ||
@@ -26,0 +26,0 @@ else { |
@@ -16,3 +16,3 @@ var assert = require('chai').assert; | ||
fallback = fallbackDecorator(function (err, a, b, c, func) { | ||
fallback = fallbackDecorator(function (a, b, c, func) { | ||
func(undefined, 'giving up'); | ||
@@ -19,0 +19,0 @@ }, undefined, logger); |
@@ -16,3 +16,3 @@ var assert = require('chai').assert; | ||
fallback = fallbackDecorator(function (err, a, b, c, func) { | ||
fallback = fallbackDecorator(function (a, b, c, func) { | ||
func(undefined, 'giving up'); | ||
@@ -19,0 +19,0 @@ }, undefined, logger); |
@@ -23,3 +23,3 @@ var assert = require('chai').assert; | ||
decorator = compose( | ||
promiseTranslator(fallbackDecorator(function (err, a, b, c, func) { | ||
promiseTranslator(fallbackDecorator(function (a, b, c, func) { | ||
func(null, 'no value'); | ||
@@ -26,0 +26,0 @@ }, Error, logger)), |
@@ -46,3 +46,3 @@ var assert = require('chai').assert; | ||
decorator = compose( | ||
fallbackDecorator(function (err, a, b, c, func) { | ||
fallbackDecorator(function (a, b, c, func) { | ||
func(null, 'no value'); | ||
@@ -49,0 +49,0 @@ }, Error, logger), |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
302
74518
2091