Skip to content

Instantly share code, notes, and snippets.

@moodysalem
Created December 22, 2018 04:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moodysalem/e0445bcad995eb1c4f8d974434410967 to your computer and use it in GitHub Desktop.
Save moodysalem/e0445bcad995eb1c4f8d974434410967 to your computer and use it in GitHub Desktop.
TypeScript: Higher order function to prevent duplicate calls to an async function, i.e. memoize
import { expect } from 'chai';
import preventDuplicateAsync from '../src/util/prevent-duplicate-async';
function testPromise<T>(): { promise: Promise<T>; resolve: (T) => void; reject: (Error) => void; } {
let rslv, rjct;
const promise = new Promise<T>((resolve, reject) => {
rslv = resolve;
rjct = reject;
});
return { resolve: rslv, reject: rjct, promise };
}
function wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
describe('preventDuplicateAsync', () => {
it.only('works', async () => {
const testFn = preventDuplicateAsync<{ key: string; proxy: Promise<void> }, void>(
async ({ key, proxy }) => {
await proxy;
},
({ key }) => key
);
const p1 = testPromise<void>();
const p2 = testPromise<void>();
const p3 = testPromise<void>();
const dedupedOne = testFn({ key: 'x', proxy: p1.promise });
const dedupedTwo = testFn({ key: 'x', proxy: p2.promise });
const notDedupedThree = testFn({ key: 'y', proxy: p3.promise });
// It uses the promise cache to prevent duplicate requests
expect(dedupedOne).to.eq(dedupedTwo);
expect(dedupedTwo).to.not.eq(notDedupedThree);
expect(dedupedOne).to.not.eq(notDedupedThree);
let oneResolved = false;
dedupedOne.then(() => oneResolved = true);
let twoResolved = false;
dedupedTwo.then(() => twoResolved = true);
let threeResolved = false;
notDedupedThree.then(() => threeResolved = true);
// now if we resolve p2, nothing should happen because the promise isn't used
p2.resolve(void 0);
await wait(0);
expect(oneResolved).to.eq(false, 'one is not resolved after p2');
expect(twoResolved).to.be.eq(false, 'two is not resolved after p2');
expect(threeResolved).to.be.eq(false, 'three is not resolved after p2');
// if we resolve p1, that should work
p1.resolve(void 0);
await wait(0);
expect(oneResolved).to.be.eq(true, 'one is resolved after p1');
expect(twoResolved).to.be.eq(true, 'two is resolved after p1');
expect(threeResolved).to.be.eq(false, 'three is not resolved after p1');
p3.resolve(void 0);
await wait(0);
expect(oneResolved).to.be.eq(true, 'one is resolved after p3');
expect(twoResolved).to.be.eq(true, 'two is resolved after p3');
expect(threeResolved).to.be.eq(true, 'three is resolved after p3');
});
});
export default function preventDuplicateAsync<TArgs, TResult>(
func: (TArgs) => Promise<TResult>,
keyFunction: (TArgs) => string
) {
let promiseCache: { [ key: string ]: Promise<TResult> } = {};
return function deduped(args: TArgs): Promise<TResult> {
const key = keyFunction(args);
if (promiseCache[ key ]) {
return promiseCache[ key ];
}
return (promiseCache[ key ] = func(args)
.then(result => {
delete promiseCache[ key ];
return result;
})
.catch(error => {
delete promiseCache[ key ];
throw error;
}));
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment