limit-once
Advanced tools
Comparing version 0.7.0 to 0.8.0
@@ -1,2 +0,1 @@ | ||
import { bind } from "bind-event-listener"; | ||
function onceAsync(fn) { | ||
@@ -6,3 +5,2 @@ let state = { | ||
}; | ||
let controller = new AbortController(); | ||
function cached(...args) { | ||
@@ -15,7 +13,8 @@ if (state.type === "fulfilled") { | ||
} | ||
let rejectPendingPromise; | ||
function abort() { | ||
rejectPendingPromise?.(); | ||
} | ||
const promise = new Promise((resolve, reject) => { | ||
function listener() { | ||
reject(); | ||
} | ||
const cleanup = bind(controller.signal, { type: "abort", listener: () => reject() }); | ||
rejectPendingPromise = reject; | ||
fn.call(this, ...args).then((result) => { | ||
@@ -30,7 +29,8 @@ state = { | ||
reject(...args2); | ||
}).finally(cleanup); | ||
}); | ||
}); | ||
state = { | ||
type: "pending", | ||
promise | ||
promise, | ||
abort | ||
}; | ||
@@ -40,4 +40,5 @@ return promise; | ||
cached.clear = function clear() { | ||
controller.abort(); | ||
controller = new AbortController(); | ||
if (state.type === "pending") { | ||
state.abort(); | ||
} | ||
state = { | ||
@@ -44,0 +45,0 @@ type: "initial" |
@@ -13,3 +13,3 @@ { | ||
"license": "MIT", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"private": false, | ||
@@ -34,5 +34,3 @@ "repository": { | ||
}, | ||
"dependencies": { | ||
"bind-event-listener": "^3.0.0" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
@@ -39,0 +37,0 @@ "@types/bun": "latest", |
# limit-once | ||
Create a `once` function that caches the result of the first function call. `limit-once` let's you lazily evaluate a value (using a function), and then hold onto the value forever. | ||
Gives you the ability to ensure a `function` is only called `"once"`, and that that the result of that single `function` call is returned every time. | ||
@@ -10,4 +10,4 @@ > [!NOTE] | ||
- [Synchronous variant](#synchronous-variant) (`150B`) | ||
- [Asynchronous variant for promises](#asynchronous-variant) (`460B`) | ||
- [Synchronous variant](#synchronous-variant) (tiny `150B`) | ||
- [Asynchronous variant for promises](#asynchronous-variant) (tiny `372B`) | ||
- Only include the code for the variant(s) you want | ||
@@ -33,2 +33,4 @@ - Both variants support cache clearing (avoid memory leaks) | ||
Create a new `function` that wraps an existing function, where the wrapped function is only called once. | ||
```ts | ||
@@ -56,2 +58,29 @@ import { once } from 'limit-once'; | ||
If the function being wrapped `throw`s an error, then that `throw` is not cached, and the wrapped function is allowed to be called again | ||
```ts | ||
import { once } from 'limit-once'; | ||
let callCount = 0; | ||
function maybeThrow({ shouldThrow }: { shouldThrow: boolean }): string { | ||
callCount++; | ||
if (shouldThrow) { | ||
throw new Error(`Call count: ${callCount}`); | ||
} | ||
return `Call count: ${callCount}`; | ||
} | ||
const maybeThrowOnce = once(maybeThrow); | ||
expect(() => maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 1'); | ||
// failure result was not cached, underlying `maybeThrow` called again | ||
expect(() => maybeThrowOnce({ shouldThrow: true })).toThrowError('Call count: 2'); | ||
// our first successful result will be cached | ||
expect(maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3'); | ||
expect(maybeThrowOnce({ shouldThrow: false })).toBe('Call count: 3'); | ||
``` | ||
### Cache clearing (`.clear()`) | ||
@@ -108,3 +137,3 @@ | ||
A "rejected" promise call will not be cached and will allow the wrapped function to be called again | ||
If the wrapped function that returns a promise has it's promise `"rejected"`, then the call will not be cached, and the underlying function can be called again. | ||
@@ -179,3 +208,3 @@ ```ts | ||
If onced async function is `"pending"` when `.clear()` is called, then the promise will be rejected. | ||
If onced async function is `"pending"` when `.clear()` is called, then the promise(s) that the onced function has returned will be rejected. | ||
@@ -192,8 +221,15 @@ ```ts | ||
const promise1 = getNameOnce().catch(() => { | ||
console.log('rejected'); | ||
console.log('rejected promise 1'); | ||
}); | ||
const promise2 = getNameOnce().catch(() => { | ||
console.log('rejected promise 2'); | ||
}); | ||
// cached cleared while promise was pending | ||
// will cause `promise1` to be rejected | ||
getNameOnce.clear(); | ||
// console.log → "rejected promise 1" | ||
// console.log → "rejected promise 2" | ||
``` |
@@ -1,3 +0,1 @@ | ||
import { bind } from 'bind-event-listener'; | ||
type ResultValue<TFunc extends (this: any, ...args: any[]) => Promise<any>> = Awaited< | ||
@@ -14,3 +12,3 @@ ReturnType<TFunc> | ||
| { type: 'initial' } | ||
| { type: 'pending'; promise: Promise<T> } | ||
| { type: 'pending'; promise: Promise<T>; abort: () => void } | ||
| { type: 'fulfilled'; result: T }; | ||
@@ -27,4 +25,2 @@ | ||
let controller = new AbortController(); | ||
function cached( | ||
@@ -45,8 +41,9 @@ this: ThisParameterType<TFunc>, | ||
let rejectPendingPromise: (() => void) | null; | ||
function abort() { | ||
rejectPendingPromise?.(); | ||
} | ||
const promise: Promise<Result> = new Promise((resolve, reject) => { | ||
function listener() { | ||
reject(); | ||
} | ||
const cleanup = bind(controller.signal, { type: 'abort', listener: () => reject() }); | ||
rejectPendingPromise = reject; | ||
fn.call(this, ...args) | ||
@@ -64,7 +61,3 @@ .then((result: Result) => { | ||
reject(...args); | ||
}) | ||
// this isn't needed for functionality, | ||
// but it seems like a good idea to unbind the event listener | ||
// to prevent possible memory leaks | ||
.finally(cleanup); | ||
}); | ||
}); | ||
@@ -75,2 +68,3 @@ | ||
promise, | ||
abort, | ||
}; | ||
@@ -82,6 +76,6 @@ | ||
cached.clear = function clear() { | ||
controller.abort(); | ||
// Need to create a new controller | ||
// as the old one has been aborted | ||
controller = new AbortController(); | ||
if (state.type === 'pending') { | ||
state.abort(); | ||
} | ||
state = { | ||
@@ -88,0 +82,0 @@ type: 'initial', |
Sorry, the diff of this file is not supported yet
18709
1
230
333
- Removedbind-event-listener@^3.0.0
- Removedbind-event-listener@3.0.0(transitive)