medikoo/memoizee

Rejected Promise stays in Cache

john681611 opened this issue · 3 comments

We seem to be finding that rejected promises are still being stored in the cache.
Here is the test we are using.
Modules used: Mocha,Sinon,Chai,Chai-as-promised;

it.only('should not cache rejected promises', function (done) {
        // Given
        const memoizee = require('memoizee');
        let defaultResult = 1;
        let stub = sinon.stub().returns(Promise.reject(new Error('Bang!')));
        let memoized = memoizee(stub, { promise: true });

        // When
        expect(memoized()).to.be.rejectedWith(Error);


        stub.returns(Promise.resolve(defaultResult));

        // Then
        let result = memoized();
        expect(stub).to.have.been.calledTwice;
        expect(result).to.eventually.equal(defaultResult).notify(done);
    });

Output:

expected stub to have been called exactly twice, but it was called once
stub() => [ZoneAwarePromise] { __zone_symbol__state: false, __zone_symbol__value: Error: Bang! } at memoized (/Users/c2444781/Documents/mns-fe-foundation/node_modules/memoizee/lib/configure-map.js:81:23)
AssertionError: expected stub to have been called exactly twice, but it was called once
stub() => [ZoneAwarePromise] { __zone_symbol__state: false, __zone_symbol__value: Error: Bang! } at memoized (node_modules/memoizee/lib/configure-map.js:81:23)
at Context. (src/components/memoize/memoize.test.js:219:34)

@john681611 rejected promise is released from cache after rejection is discovered. Here you run second test before rejection is discovered.

It all comes down to design of promises. You're not capable to get value of a promise synchronously, but earliest in next event loop. Therefore even if memoized function immediately resolves with rejected promise, the memoizee internals can learn about it earliest in next event loop.

Following test case should pass for you:

it.only('should not cache rejected promises', function (done) {
  // Given
  const memoizee = require('memoizee');
  let defaultResult = 1;
  let stub = sinon.stub().returns(Promise.reject(new Error('Bang!')));
  let memoized = memoizee(stub, { promise: true });

  // When
  expect(memoized()).to.be.rejectedWith(Error);

  stub.returns(Promise.resolve(defaultResult));

  // Then
  setTimeout(() => {
    let result = memoized();
    expect(stub).to.have.been.calledTwice;
    expect(result).to.eventually.equal(defaultResult).notify(done);
  });
});

The code in the setTimout never fires

The code in the setTimeout never fires

Then there must be some issue with your setup. It's hard for me to see why setTimeout doesn't work on your side. You may prepare some reproducible test case on codepen or jsbin, from that we can move forward