Comments (15)
Switched to Promise<V | undefined>
in v10.
from node-lru-cache.
Well, the issue is that the promise might resolve without a value if the fetchMethod doesn't return a value when it's aborted. Which, there's no reason to require it to return undefined
rather than void
, especially since JavaScript doesn't even make a distinction between the two.
What problem does it cause to have void
there?
At the very least, removing the void
would be a semver major change.
Here's an example:
import { LRUCache } from './'
const c = new LRUCache<number, string>({
max: 3,
fetchMethod: async (key, _oldValue, { signal }) => {
await new Promise<void>(res => setTimeout(() => res(), 100))
if (signal.aborted) {
console.error('aborted, returning void', key)
// this would have to be `return undefined`, whereas `return` is allowed now,
// because fetch() resolves to the promise that the fetchMethod returns.
return
}
return String(key)
},
ignoreFetchAbort: true,
allowStaleOnFetchAbort: true,
})
Promise.all([
c.fetch(1),
c.fetch(2),
c.fetch(3),
c.fetch(4),
]).then(console.log)
$ ts-node fetch-void.ts
aborted, returning void 1
[ undefined, '2', '3', '4' ]
from node-lru-cache.
I just run into the same issue. Worst case scenario it should be undefined | V
isn't it?
from node-lru-cache.
If the fetch is aborted due to the key being evicted or deleted prior to the fetchMethod returning, then it might not return a value.
It's an edge case, but it is possible, and the previous declared type was incorrect by not including it.
For full type safety, test that the return type is the type you want, and handle the error case. If you know that can't happen and wanna yolo it, you can coerce it with an explicit as
.
from node-lru-cache.
If you know that can't happen and wanna yolo it, you can coerce it with an explicit as.
Indeed that is what I've done in my case but to be fair it doesn't seem like a really good practice. If you Lru-cache change the behavior (and the returned types) of the fetch method I won't be warned.
Could returning undefined
or null
in this case make everyone happy?
from node-lru-cache.
What problem does it cause to have void there?
That's just an issue when writing pure TS but I guess that void
is more appropriate when nothing occurs (aborted).
Monkey patch - We don't want that fetch return void
but undefined instead
@HuyAms we can use a monkey patch like this in our case.
class LRUCacheMonkeyPatched<K extends {}, V extends {}, FC extends unknown = unknown> extends LRUCache<K, V, FC> {
override async fetch(
k: K,
): Promise<undefined | V> {
const data = await super.fetch(k as never);
// When we fetch void let's return undefined
if (typeof data === 'undefined') {
return undefined;
}
return data;
}
}
Or even more straight forward
override async fetch(
k: K,
): Promise<undefined | V> {
return super.fetch(k as never) as unknown as undefined | V;
}
from node-lru-cache.
I write pure TS, but what's the issue, exactly? What situation are you using it in, and what error occurs? I'm ok with making it undefined instead of void, just want to understand why.
The reason I'm pushing back a bit on this is that it means changing the type signature of a public method in an incompatible way, so it's a SemVer breaking change, as it may require downstream users to update their code.
from node-lru-cache.
I hit the issue working on this PR for an open-source project. I ended up doing the yolo mode (coercing it with an explicit as
), but that's not our standard/guidelines.
If we don't use the explicit cast. We can't use the optional chaining operator
to explicitly say "That's ok, this value might be null or undefined".
permissions
has just been fetched from the LRU cache. If for some normal reason or obscure reason, it doesn't resolve an expected value we handle this using some optional chaining operator
from node-lru-cache.
According to the TS documentation:
void represents the return value of functions which don’t return a value. It’s the inferred type any time a function doesn’t have any return statements, or doesn’t return any explicit value from those return statements:
The fetch
function has multiple return
statements, and every one of them is returning a value. The keyword void
is not appropriate here according to the documentation.
The problem with void when using this function is that TS does not treat it as if it was undefined, even if in JS there is no difference. So in this case void | V
is not equivalent to undefined | V
and it does not cast easily to this type.
from node-lru-cache.
@ghusse It doesn't return actual void
, it returns Promise<V | undefined | void>
, and there is a code path where it returns the return value of fetchMethod
, which may be a Promise<void>
. "Return" is being used as a shorthand for "return a promise that resolves to", because it's an async function.
from node-lru-cache.
Hey there. I see the reason for having void
as one of the possible promise outcomes. However, testing whether an actual value is returned becomes a bit harder as we cannot simply check for undefined
. Instead, we have to check for what we expect (for example, an object). At least I had some problems satisfying TS in this regard.
Below is an example of a simple wrapper which takes a function, initialises a cache and returns a cached version of the input function (same signature). Upon void
, an error is thrown, assuming we don't expect aborts and the like. This may help.
export function memoizeLru<Fn extends (...args: Args) => Promise<Result>, Args extends any[], Result extends object>(
fn: Fn,
options:
| Omit<LRUCache.OptionsMaxLimit<string, Result, { args: Args }>, 'fetchMethod'>
| Omit<LRUCache.OptionsTTLLimit<string, Result, { args: Args }>, 'fetchMethod'>,
resolveKey: (...args: Args) => string
): (...args: Args) => Promise<Result> {
const cache = new LRUCache<string, Result, { args: Args }>({
...options,
fetchMethod: async (_key, _staleValue, { context }) => {
return await fn(...context.args);
}
});
return async (...args: Args) => {
const result = await cache.fetch(resolveKey(...args), { context: { args } });
if (!(result instanceof Object)) {
// result might be of type void (in case of abort)
// should never happen here (we use no signals)
throw new Error('fetch aborted');
}
return result;
};
}
Hey, and thanks for this great lib ❤️
from node-lru-cache.
The TS doc is pretty clear about the usage of void : as soon as a function CAN return something, void should not be used
from node-lru-cache.
@ghusse There is a difference between returning void
and Promise<void>
and Promise<void|X|Y>
. Apart from that, whether fn's return type is void
or contains void
solely depends on what TS infers based on the paths and return statements of the function, which means that you cannot choose to use void or not. However, you can choose not to mix returns without a value and returns with a value in the same function. If that's what you mean, I haven't seen a statement in the TS docs which forbids this. Maybe there is a linter rule out there ...
from node-lru-cache.
Ok, so, semantic details about what specifically void
means, when to use it, whether it's always/never/ever a good idea, etc, is kind of off topic here.
The point is, LRUCache.fetch()
can return Promise<void>
because it returns the promise returned by the fetchMethod
option, and that can return Promise<void>
. It's not an ideological point worth debating, tbh, it was just allowed in the JS version for the function to not return anything when it receives an abort signal, and it got ported through to the TS version. TS is weird about this, it is what it is.
It's not super hard to make it Promise<V | undefined>
without changing the fetchMethod
signature qv. But it does of course change the fetch()
signature, so it's a semver major change.
It'll be fixed in version 10. I've been dragging my feet just because I wanted to make sure it's worth the disruption when bumping major on a project like this, and also because I'm feeling lazy, but I guess it's time to update all of my projects again lol.
from node-lru-cache.
👍 thanks a lot for the update
from node-lru-cache.
Related Issues (20)
- Remove import of lib DOM HOT 2
- Cannot read private member #keyMap from an object whose class did not declare it HOT 5
- Breaking change on version 7.15.0 - "Uncaught TypeError: LRU is not a constructor" HOT 2
- Engines constraint HOT 1
- Ever endpoint request, cache is empty, what can I fix? HOT 2
- Structural setup around now forbidden undefined and null values (since v8) HOT 2
- dispose/disposeAfter aren't called on replace HOT 1
- Question about cache eviction HOT 3
- Issue with lru-cache npm module: Unexpected token error and unresolved problems HOT 4
- when i refresh the page,the cache data is clear HOT 1
- Nuxt uses lru-cache to refresh the page causing the cache to be empty HOT 1
- Unexpected token '(' HOT 8
- Private field '#head' must be declared in an enclosing class HOT 2
- `ttl` documentation HOT 5
- Cache size HOT 1
- Not working with IOS14.x.x versions HOT 1
- Is the signal passed to the fetchMethod always triggering the abort event? HOT 1
- Advice on avoiding fetches being aborted due to eviction HOT 3
- Cache in Remix app always empty? HOT 2
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 node-lru-cache.