-
-
Notifications
You must be signed in to change notification settings - Fork 283
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
The new asyncMemoize function should memoize the original promise #763
Comments
Sorry, I think you're a little mistaken, look at code: const getGlobby = asyncMemoize(async () => {
// @ts-ignore
const { globby } = await import("globby");
return globby;
});
/**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): Promise<T>}
*/
function asyncMemoize(fn) {
let cache = false;
/** @type {T} */
let result;
return async () => {
if (cache) {
return result;
}
result = await /** @type {function(): any} */ (fn)();
cache = true;
// Allow to clean up memory for fn
// and all dependent resources
// eslint-disable-next-line no-undefined, no-param-reassign
fn = undefined;
return result;
};
} When I call
So we don't run
We already do it, but because we need to make an async operation we can't just return Why not just Anyway feel free to feedback |
Ok, let's run the
Now, let's run the
I think that this should be fixed mostly because IMO Also, Node already caches modules, so maybe the memoizing part should be removed entirely and just the lazy loading part should be kept. E.g. const getGlobby = async () => {
// @ts-ignore
const { globby } = await import("globby");
return globby;
}; |
We have
Yes, Node.js already caches modules, but caching on Node.js side is not fast as we want, just try it.
It works, you are saying about caching different things, that is why you are thinking it doesn't work 😄 Anyway I made benchmark: /**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): T}
*/
function memoize(fn) {
let cache = false;
/** @type {T} */
let result;
return () => {
if (cache) {
return result;
}
result = /** @type {function(): any} */ (fn)();
cache = true;
// Allow to clean up memory for fn
// and all dependent resources
// eslint-disable-next-line no-undefined, no-param-reassign
fn = undefined;
return result;
};
}
/**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): Promise<T>}
*/
function asyncMemoize(fn) {
let cache = false;
/** @type {T} */
let result;
return async () => {
if (cache) {
return result;
}
result = await /** @type {function(): any} */ (fn)();
cache = true;
// Allow to clean up memory for fn
// and all dependent resources
// eslint-disable-next-line no-undefined, no-param-reassign
fn = undefined;
return result;
};
}
const getGlobby = memoize(async () => {
// @ts-ignore
const { globby } = await import("globby");
return globby;
});
const getGlobbyAsync = asyncMemoize(async () => {
// @ts-ignore
const { globby } = await import("globby");
return globby;
});
const { Benchmark } = require("benchmark");
const suite = new Benchmark.Suite();
function p(fn) {
return {
defer: true,
async fn(deferred) {
await fn();
deferred.resolve();
},
};
}
// Warn up
(async function warnup() {
await import("globby");
})();
// add tests
suite
.add(
'await import("globby")',
p(async () => {
await import("globby");
}),
)
.add(
"await getGlobby()",
p(async () => {
await getGlobby();
}),
)
.add(
"await getGlobbyAsync()",
p(async () => {
await getGlobbyAsync();
}),
)
// add listeners
.on("cycle", (event) => {
console.log(String(event.target));
})
.on("complete", function () {
console.log(`Fastest is ${this.filter("fastest").map("name")}`);
})
// run async
.run({ async: true }); And got:
So caching the original promises is faster, I will fix it |
Thanks! So the bug won't happen now, but just to be clear, the problem with E.g. const promise1 = getGlobbyAsync() // No await
// cache is still false at this point
const promise2 = getGlobbyAsync() // This calls import a second time
const [globby1, globby2] = await Promise.all(promise1, promise2)
// From here onwards cache is true, so getGlobbyAsync won't call import again |
Modification Proposal
The new
asyncMemoize
function introduced in v12.0.1 (#760) waits until the promise returned by the memoized function is resolved before "activating" the cache.Relevant snippet:
The problem is that if the function returned by
asyncMemoize
is called again before the first function resolves (or rejects), the memoized function is executed a second time.In my opinion,
asyncMemoize
should do exactly whatmemoize
does when memoizing an async function: The returned function returns the promise that the memoized function returns.I guess
asyncMemoize
exists because of TypeScript? I don't use TypeScript, so I don't know what is being solved by having a dedicated function to memoize async functions instead of just usingmemoize
.Expected Behavior / Situation
A function returned by
asyncMemoize
should not execute the memoized function more than once. Subsequent executions of the returned function should return the value that the memoized function returned the first time it was executed.Actual Behavior / Situation
A function returned by
asyncMemoize
can execute the memoized function a second time (or more) if the returned function is executed a second time when the promise returned by the memoized function is still pending.Please paste the results of
npx webpack-cli info
here, and mention other relevant informationThis is not a problem yet, but it could be, so better fix it before it becomes a problem.
The text was updated successfully, but these errors were encountered: