reuse-promise
Advanced tools
Comparing version 1.0.1 to 1.1.0
# Change Log | ||
## [1.1.0] - 2016-07-06 | ||
- Added `serializeArguments` option [#1](https://github.com/elado/reuse-promise/pull/1) ([@Tyler-Murphy](https://github.com/Tyler-Murphy)) | ||
## [1.0.0] - 2016-04-18 | ||
- Initial release |
161
lib/index.js
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
"use strict"; | ||
@@ -7,11 +7,15 @@ Object.defineProperty(exports, "__esModule", { | ||
var _promise = require('babel-runtime/core-js/promise'); | ||
var _keys = require("babel-runtime/core-js/object/keys"); | ||
var _keys2 = _interopRequireDefault(_keys); | ||
var _promise = require("babel-runtime/core-js/promise"); | ||
var _promise2 = _interopRequireDefault(_promise); | ||
var _assign = require('babel-runtime/core-js/object/assign'); | ||
var _extends2 = require("babel-runtime/helpers/extends"); | ||
var _assign2 = _interopRequireDefault(_assign); | ||
var _extends3 = _interopRequireDefault(_extends2); | ||
var _stringify = require('babel-runtime/core-js/json/stringify'); | ||
var _stringify = require("babel-runtime/core-js/json/stringify"); | ||
@@ -26,99 +30,20 @@ var _stringify2 = _interopRequireDefault(_stringify); | ||
// TODO compare key with shallow equal, not JSON | ||
// TODO equality check function | ||
var _allPromiseMapsByArgs = []; | ||
var _allMemoizedValueMapsByArgs = []; | ||
var pendingPromisesMap = function () { | ||
var FN_INDEX = 0; | ||
var KEY_INDEX = 1; | ||
var VALUE_INDEX = 2; | ||
function serializeArguments(key) { | ||
return (0, _stringify2.default)(key); | ||
} | ||
var a = []; | ||
function serializeKey(key) { | ||
return (0, _stringify2.default)(key); | ||
} | ||
function is(item, fn, serializedKey) { | ||
if (item[FN_INDEX] == fn && item[KEY_INDEX] == serializedKey) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
function getIndex(fn, serializedKey) { | ||
for (var i = 0, l = a.length; i < l; i++) { | ||
var item = a[i]; | ||
if (is(item, fn, serializedKey)) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
function get(fn, key) { | ||
var serializedKey = serializeKey(key); | ||
var i = getIndex(fn, serializedKey); | ||
if (a[i]) { | ||
return a[i][VALUE_INDEX]; | ||
} | ||
return null; | ||
} | ||
function set(fn, key, value) { | ||
var serializedKey = serializeKey(key); | ||
var i = getIndex(fn, serializedKey); | ||
if (i > -1) { | ||
a[i][VALUE_INDEX] = value; | ||
} else { | ||
var obj = []; | ||
obj[FN_INDEX] = fn; | ||
obj[KEY_INDEX] = serializedKey; | ||
obj[VALUE_INDEX] = value; | ||
a.push(obj); | ||
} | ||
return value; | ||
} | ||
function del(fn, key) { | ||
if (key === undefined) { | ||
// delete all of function | ||
for (var i = a.length - 1; i >= 0; i--) { | ||
var item = a[i]; | ||
if (item[FN_INDEX] == fn) { | ||
a.splice(i, 1); | ||
} | ||
} | ||
} else { | ||
// delete single entry by function and key | ||
var serializedKey = serializeKey(key); | ||
var _i = getIndex(fn, serializedKey); | ||
if (_i > -1) { | ||
a.splice(_i, 1); | ||
} | ||
} | ||
} | ||
function clear() { | ||
a.length = 0; | ||
} | ||
return { | ||
get: get, | ||
set: set, | ||
del: del, | ||
clear: clear | ||
}; | ||
}(); | ||
function reusePromise(origFn) { | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
options = (0, _assign2.default)({ memoize: false }, options); | ||
options = (0, _extends3.default)({ | ||
memoize: false, | ||
serializeArguments: serializeArguments | ||
}, options); | ||
var promiseMapsByArgs = {}; | ||
var memoizedValuesByArgs = {}; | ||
var wrappedFn = function wrappedFn() { | ||
@@ -129,21 +54,12 @@ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
var key = args; | ||
var memoizeValueKey = ['memoized', key]; | ||
var key = options.serializeArguments(args); | ||
var pendingPromise = pendingPromisesMap.get(origFn, key); | ||
var pendingPromise = promiseMapsByArgs[key]; | ||
if (pendingPromise) { | ||
return pendingPromise; | ||
} | ||
if (pendingPromise) return pendingPromise; | ||
if (options.memoize) { | ||
var prevValue = pendingPromisesMap.get(origFn, memoizeValueKey); | ||
if (options.memoize && key in memoizedValuesByArgs) return _promise2.default.resolve(memoizedValuesByArgs[key]); | ||
if (prevValue) { | ||
return _promise2.default.resolve(prevValue); | ||
} | ||
} | ||
var forgetPromise = function forgetPromise() { | ||
pendingPromisesMap.del(origFn, key); | ||
return delete promiseMapsByArgs[key]; | ||
}; | ||
@@ -154,6 +70,3 @@ | ||
var promise = origPromise.then(function (value) { | ||
if (options.memoize) { | ||
pendingPromisesMap.set(origFn, memoizeValueKey, value); | ||
} | ||
if (options.memoize) memoizedValuesByArgs[key] = value; | ||
forgetPromise(); | ||
@@ -165,3 +78,3 @@ return value; | ||
}); | ||
pendingPromisesMap.set(origFn, key, promise); | ||
promiseMapsByArgs[key] = promise; | ||
@@ -173,4 +86,8 @@ return promise; | ||
wrappedFn.__reusePromise__clear = function () { | ||
reusePromise.clear(origFn); | ||
reusePromise.clear(wrappedFn); | ||
}; | ||
wrappedFn.__reusePromise__promiseMapsByArgs = promiseMapsByArgs; | ||
wrappedFn.__reusePromise__memoizedValuesByArgs = memoizedValuesByArgs; | ||
_allPromiseMapsByArgs.push(promiseMapsByArgs); | ||
_allMemoizedValueMapsByArgs.push(memoizedValuesByArgs); | ||
@@ -180,2 +97,8 @@ return wrappedFn; | ||
function clearObject(object) { | ||
(0, _keys2.default)(object).forEach(function (k) { | ||
return delete object[k]; | ||
}); | ||
} | ||
function clear() { | ||
@@ -185,5 +108,7 @@ var fn = arguments.length <= 0 || arguments[0] === undefined ? undefined : arguments[0]; | ||
if (fn === undefined) { | ||
pendingPromisesMap.clear(); | ||
_allPromiseMapsByArgs.forEach(clearObject); | ||
_allMemoizedValueMapsByArgs.forEach(clearObject); | ||
} else { | ||
pendingPromisesMap.del(fn.__reusePromise__origFn || fn); | ||
clearObject(fn.__reusePromise__promiseMapsByArgs); | ||
clearObject(fn.__reusePromise__memoizedValuesByArgs); | ||
} | ||
@@ -190,0 +115,0 @@ } |
{ | ||
"name": "reuse-promise", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Reuse the same promise until resolved when retrieving it from a function", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -7,4 +7,6 @@ # reuse-promise | ||
When a function returns a promise and it's being called from multiple places in the app, new promises are being instantiated, and multiple async operations are be executed. | ||
> **TL;DR** - Prevent from a unique async process (function that returns a promise) to run more than once concurrently by temporarily caching the promise until it's resolved/rejected. | ||
When a function returns a promise and it's being called from multiple places in the app, new promises are being instantiated, and multiple async operations are going to be executed. | ||
A common case is a function that gets an `articleId` and returns a promise that calls API. This function can be called from multiple places, each time will create a new promise and will issue a new request. This is usually not desired: | ||
@@ -14,7 +16,5 @@ | ||
function findArticle(articleId) { | ||
return new Promise((resolve, reject) => { | ||
fetch(`/article/${articleId}`).then(r => r.json()).then(function (data) { | ||
resolve(data) | ||
}) | ||
}) | ||
return fetch(`/article/${articleId}`).then(r => r.json()) | ||
// could also be | ||
// return new Promise(...) | ||
} | ||
@@ -30,8 +30,6 @@ | ||
`reuse-promise` decorates a function and temporary memoizes a promise until it's resolved. In this case, the first call for `articleId=1` will create the new promise, issue the HTTP request, and remember that created promise for `articleId=1`. The second call with the same argument will return the same promise from earlier call. | ||
`reuse-promise` decorates a function and **temporary** memoizes a promise until it's resolved. In this case, the first call for `articleId=1` will create the new promise, issue the HTTP request, and remember that created promise for `articleId=1`. The second call with the same argument will return the same promise from earlier call. However, once the original promise is resolved (or rejected), a new call to `findArticle(1)` will issue a new request. | ||
Promises are kept in cache and returned without recreating a new one only while they are in progress. When the promise is resolved, it'll be cleared from this temporary cache, allowing a new call to `findArticle(1)` to recreate a promise and issue an HTTP request. | ||
An initial call to a wrapped function goes through the original function, and then indexes the returned promise by a json-serialized string of the arguments that were sent to the function. So `findArticles([1, 2, 3])` can be called twice and still return the same promise, becasue `JSON.stringify([1, 2, 3]) === JSON.stringify([1, 2, 3])`. | ||
Promises are kept in an index by the arguments that were sent to the function, so `findArticle(1)` and `findArticles([1, 2, 3])` will go through the original function and create a new promise, and any following call with the same arguments will reuse the same promise. The comparison between two sets of arguments is by `JSON.stringify` the argument array, hence a call with a new array [1, 2, 3] will still reuse the same promise. | ||
## Installation | ||
@@ -47,3 +45,3 @@ | ||
### As a class decoartor | ||
### As a class decorator | ||
@@ -58,7 +56,3 @@ Requires `babel` and `babel-plugin-transform-decorators-legacy` plugin. | ||
find(articleId) { | ||
return new Promise((resolve, reject) => { | ||
fetch(`/article/${articleId}`).then(r => r.json()).then(function (data) { | ||
resolve(data) | ||
}) | ||
}) | ||
return fetch(`/article/${articleId}`).then(r => r.json()) | ||
} | ||
@@ -83,7 +77,3 @@ } | ||
function findArticle(articleId) { | ||
return new Promise((resolve, reject) => { | ||
fetch(`/article/${articleId}`).then(r => r.json()).then(function (data) { | ||
resolve(data) | ||
}) | ||
}) | ||
return fetch(`/article/${articleId}`).then(r => r.json()) | ||
} | ||
@@ -112,7 +102,3 @@ | ||
find(articleId) { | ||
return new Promise((resolve, reject) => { | ||
fetch(`/article/${articleId}`).then(r => r.json()).then(function (data) { | ||
resolve(data) | ||
}) | ||
}) | ||
return fetch(`/article/${articleId}`).then(r => r.json()) | ||
} | ||
@@ -149,2 +135,26 @@ } | ||
### option: `serializeArguments` | ||
By default, `reuse-promise` indexes promises in a dictionarty where the key is all arguments `JSON.stringify`ied. This is sometimes an unnecessary process, especially when sending big objects as arguments. | ||
A custom argument serializer can be provided. To reuse promises based on the first letter of the first argument, for example, provide: | ||
```js | ||
@reusePromise({ | ||
serializeArguments: args => args[0][0] | ||
}) | ||
``` | ||
Or, to grab an ID of a given model without having it all serialized: | ||
```js | ||
updateUserName = reusePromise(updateUserName, { | ||
serializeArguments: args => args[0].id | ||
}) | ||
const someUser = { id: 1, name: 'name' } | ||
updateUserName(someUser, 'new name') | ||
``` | ||
## Test | ||
@@ -151,0 +161,0 @@ |
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
162
11394
85