Comments (4)
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 asyncMemoize
happens when the memoized function is called a second time before the Promise returned by the first call resolves.
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
from copy-webpack-plugin.
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 getGlobby()
:
- We don't have
cache
, so we run memorized function (usingawait
), i.e. where we are havingconst { globby } = await import("globby");
- After primise was resolved we have
cache = globby
;, i.e. we cachedglobby
function - But because we have
return async () => { /* logic */ }
we are returningPromise
and we needawait
it
So we don't run import("globby")
multiple time, only once.
A function returned by asyncMemoize should not execute the memoized function more than once.
memoize
works the same, every call getSomething()
calls returned function and check cache, no cache - run memorized function.
Subsequent executions of the returned function should return the value that the memoized function returned the first time it was executed.
We already do it, but because we need to make an async operation we can't just return globby
, only Promise<globby>
Why not just memoize
? Because in such case we will return memorized function and every await
will run code with import(...)
and there's no point in doing this, you can just write await import("globby")
Anyway feel free to feedback
from copy-webpack-plugin.
Ok, let's run the asyncMemoize
globby example step by step:
getBlobby
is assigned a memoized function:const getGlobby = asyncMemoize(async () => { // @ts-ignore const { globby } = await import("globby"); return globby; });
- On this step, the memoized function has
cache === false
andresult === undefined
, andfn
hasn't been called yet.
- On this step, the memoized function has
getBlobby
is called and returns a PromisegetBlobby
seescache === false
, so it callsfn
.- The Promise returned by
getBlobby
is not fulfilled until the promise returned byfn
is fulfilled. cache
is stillfalse
, asgetBlobby
is awating the Promise returned byfn
- If at this point
getBlobby
is called again,fn
will be called again, becausecache === false
. - When the Promise returned by the first call to
fn
resolves,cache
is set totrue
,result
is set to the resolved value and the Promise returned by the first call togetBlobby
is resolved withresult
. - When the Promise returned by second the call to
fn
resolves,cache
is set totrue
(no changes),result
is set to the new resolved value and the Promise returned by the second call togetBlobby
is resolved with the newresult
. - From this point onwards, calls to
getBlobby
don't callfn
.The returned Promise is a new Promise each time, and the resolved value is one corresponding to the second call tofn
.
Now, let's run the memoize
(not async) globby example step by step:
getBlobby
is assigned a memoized function:const getGlobby = memoize(async () => { // @ts-ignore const { globby } = await import("globby"); return globby; });
- On this step, the memoized function has
cache === false
andresult === undefined
, andfn
hasn't been called yet.
- On this step, the memoized function has
getBlobby
is called and returns a PromisegetBlobby
seescache === false
, so it callsfn
.- Because there is no
await
,getBlobby
immediately setscache = true
andresult
is assigned the Promise returned byfn
. - The Promise returned by
getBlobby
is not fulfilled until the promise returned byfn
is fulfilled, because they are the same Promise.
- If at this point
getBlobby
is called again,fn
will not be called again, becausecache === true
.- The Promise returned by this call to
getBlobby
is the same promise returned by the first call togetBlobby
, which is the same Promise thatfn
returned.
- The Promise returned by this call to
- When the Promise returned by
fn
resolves, the Promises returned by the first and second calls togetBlobby
are resolved with the same value, because these three Promises are the same. - From this point onwards, calls to
getBlobby
still return the same (resolved) Promise thatfn
returned.
I think that this should be fixed mostly because IMO asyncMemoize
is not working as intended.
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;
};
from copy-webpack-plugin.
The Promise returned by getBlobby is not fulfilled until the promise returned by fn is fulfilled.
cache is still false, as getBlobby is awating the Promise returned by fn
We have result = await /** @type {function(): any} */ (fn)();
, so result
is globby
and cache
is true
and on the second run we return cache
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.
Yes, Node.js already caches modules, but caching on Node.js side is not fast as we want, just try it.
I think that this should be fixed mostly because IMO asyncMemoize is not working as intended.
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:
await import("globby") x 162,695 ops/sec ±2.66% (84 runs sampled)
await getGlobby() x 5,478,279 ops/sec ±0.79% (86 runs sampled)
await getGlobbyAsync() x 4,613,087 ops/sec ±0.72% (88 runs sampled)
Fastest is await getGlobby()
So caching the original promises is faster, I will fix it
from copy-webpack-plugin.
Related Issues (20)
- Livereload is not triggered on file change HOT 15
- Getting security issues in underlying tar library HOT 1
- node version no right and no warn HOT 2
- Plugin causes build failure on netlify HOT 1
- Use the latest version to set the info field The file is still packed and compressed HOT 2
- `HookWebpackError: WebAssembly.instantiate(): Out of memory: wasm memory` in version 10.2.4 when nested in a `osc` build pipeline HOT 2
- globOptions.ignore not work expected HOT 22
- Copy files from "src/*.jpg" to "dist/" root. HOT 5
- HookWebpackError: Must use import to load ES Module HOT 3
- copy file permissions HOT 3
- Cache directory can't be configured HOT 7
- Who would use this to make nuxt2 use cesium 1.111.0? HOT 1
- Cannot use a folder named `node_modules` for the `to` path HOT 2
- Lack of useful use case HOT 2
- Resolve Snyk vulnerability warning on 9th Jan from 'serialize-javascript' package HOT 2
- ERROR in directoryPaths.map is not a function with v12.x HOT 5
- (BROKEN ON MACOS) Binary files lose executable permissions after copy. HOT 5
- HookWebpackError: Unexpected token '??=' HOT 1
- getCache is not a function HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from copy-webpack-plugin.