Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't memoize results that had an error #1466

Merged
merged 1 commit into from Jun 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/memoize.js
Expand Up @@ -14,6 +14,9 @@ function has(obj, key) {
* function results against, the callback is omitted from the hash and an
* optional hash function can be used.
*
* **Note: if the async function errs, the result will not be cached and
* subsequent calls will call the wrapped function.**
*
* If no hash function is specified, the first argument is used as a hash key,
* which may work reasonably if it is a string or a data type that converts to a
* distinct string. Note that objects and arrays will not behave reasonably.
Expand Down Expand Up @@ -63,7 +66,11 @@ export default function memoize(fn, hasher) {
queues[key] = [callback];
_fn.apply(null, args.concat(function(/*args*/) {
var args = slice(arguments);
memo[key] = args;
var err = args[0];
// #1465 don't memoize if an error occurred
if (!err) {
memo[key] = args;
}
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep this as is for now, but for v3, I'd be open to changing this behaviour, as there's a potential race condition here (similar to #1248).

If the initial call to the memoized function takes between m and n seconds to err, and a second call is made x seconds after the initial (m < x < n) then the function might only be run once depending on whether or not the initial call finished. To eliminate it, we would have to call the memoized function again if there are queued calls, and an error occurred.

Expand Down
28 changes: 22 additions & 6 deletions mocha_test/memoize.js
Expand Up @@ -100,9 +100,8 @@ describe("memoize", function() {
var fn2 = async.unmemoize(fn);
fn2(1, 2, function(err, result) {
expect(result).to.equal(3);
done();
});

done();
});

it('error', function(done) {
Expand All @@ -112,8 +111,26 @@ describe("memoize", function() {
};
async.memoize(fn)(1, 2, function (err) {
expect(err).to.equal(testerr);
done();
});
});

it('should not memoize result if error occurs', function(done) {
var testerr = new Error('test');
var fn = function (arg1, arg2, callback) {
callback(testerr, arg1 + arg2);
};
var memoized = async.memoize(fn);
memoized(1, 2, function (err) {
expect(err).to.equal(testerr);
testerr = null;

memoized(5, 6, function (err, result) {
expect(err).to.equal(null);
expect(result).to.equal(11);
done();
});
});
done();
});

it('multiple calls', function(done) {
Expand All @@ -134,10 +151,8 @@ describe("memoize", function() {
});

it('custom hash function', function(done) {
var testerr = new Error('test');

var fn = function (arg1, arg2, callback) {
callback(testerr, arg1 + arg2);
callback(null, arg1 + arg2);
};
var fn2 = async.memoize(fn, function () {
return 'custom hash';
Expand Down Expand Up @@ -205,4 +220,5 @@ describe("memoize", function() {
done();
});
});

});