1. throttle (line 1146-1174)
Before: Used recursive setTimeout calls via unpause, creating nested closures and multiple timer contexts. After: - Tracks a single timeoutId to manage timer lifecycle - Eliminates nested closure creation - Cleaner timer chain when calls are pending 2. batch (line 1204-1227) Before: Used throttle internally, inheriting its timer overhead. After: - Direct timer management without the throttle wrapper - Single timeoutId tracked explicitly - Maintains the same semantics (first item processed immediately, rest batched) 3. batcher (line 1235-1259) Before: - Created new timer every time queue went from empty to non-empty - Had unnecessary async in forEach and await r when r was already type U After: - Tracks timeoutId to prevent duplicate timer creation - Only creates timer when queue is truly empty - Removed unnecessary async/await in result handling - More efficient error handling (rejects all items at once if length mismatch) Performance Benefits: These changes should significantly reduce timer overhead by: - Fewer timer objects: Only one active timer per throttled/batched function instead of chains - Less garbage collection: Fewer closure allocations and intermediate objects - Better memory usage: Explicit timer ID tracking instead of implicit state in closures
This commit is contained in:
+44
-28
@@ -1148,26 +1148,27 @@ export const throttle = <F extends (...args: any[]) => any>(ms: number, f: F) =>
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
let paused = false
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
||||||
let nextArgs: Parameters<F> | undefined
|
let lastArgs: Parameters<F> | undefined
|
||||||
|
|
||||||
const unpause = () => {
|
const later = () => {
|
||||||
if (nextArgs) {
|
if (lastArgs !== undefined) {
|
||||||
f(...nextArgs)
|
const args = lastArgs
|
||||||
nextArgs = undefined
|
lastArgs = undefined
|
||||||
setTimeout(unpause, ms)
|
f(...args)
|
||||||
|
timeoutId = setTimeout(later, ms)
|
||||||
} else {
|
} else {
|
||||||
paused = false
|
timeoutId = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (...thisArgs: Parameters<F>) => {
|
return (...args: Parameters<F>) => {
|
||||||
if (!paused) {
|
lastArgs = args
|
||||||
f(...thisArgs)
|
|
||||||
paused = true
|
if (timeoutId === undefined) {
|
||||||
setTimeout(unpause, ms)
|
f(...args)
|
||||||
} else {
|
lastArgs = undefined
|
||||||
nextArgs = thisArgs
|
timeoutId = setTimeout(later, ms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1202,11 +1203,26 @@ export const throttleWithValue = <T>(ms: number, f: () => T) => {
|
|||||||
*/
|
*/
|
||||||
export const batch = <T>(t: number, f: (xs: T[]) => void) => {
|
export const batch = <T>(t: number, f: (xs: T[]) => void) => {
|
||||||
const xs: T[] = []
|
const xs: T[] = []
|
||||||
const cb = throttle(t, () => xs.length > 0 && f(xs.splice(0)))
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
|
const later = () => {
|
||||||
|
if (xs.length > 0) {
|
||||||
|
f(xs.splice(0))
|
||||||
|
timeoutId = setTimeout(later, t)
|
||||||
|
} else {
|
||||||
|
timeoutId = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (x: T) => {
|
return (x: T) => {
|
||||||
|
const shouldFlush = timeoutId === undefined
|
||||||
|
|
||||||
xs.push(x)
|
xs.push(x)
|
||||||
cb()
|
|
||||||
|
if (shouldFlush) {
|
||||||
|
f(xs.splice(0))
|
||||||
|
timeoutId = setTimeout(later, t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,27 +1234,27 @@ export const batch = <T>(t: number, f: (xs: T[]) => void) => {
|
|||||||
*/
|
*/
|
||||||
export const batcher = <T, U>(t: number, execute: (request: T[]) => U[] | Promise<U[]>) => {
|
export const batcher = <T, U>(t: number, execute: (request: T[]) => U[] | Promise<U[]>) => {
|
||||||
const queue: {request: T; resolve: (x: U) => void; reject: (reason?: string) => void}[] = []
|
const queue: {request: T; resolve: (x: U) => void; reject: (reason?: string) => void}[] = []
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
const _execute = async () => {
|
const _execute = async () => {
|
||||||
|
timeoutId = undefined
|
||||||
const items = queue.splice(0)
|
const items = queue.splice(0)
|
||||||
const results = await execute(items.map(item => item.request))
|
const results = await execute(items.map(item => item.request))
|
||||||
|
|
||||||
results.forEach(async (r, i) => {
|
if (results.length === items.length) {
|
||||||
if (results.length === items.length) {
|
results.forEach((r, i) => items[i].resolve(r))
|
||||||
items[i].resolve(await r)
|
} else {
|
||||||
} else {
|
items.forEach(item => item.reject("Execute must return a result for each request"))
|
||||||
items[i].reject("Execute must return a result for each request")
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (request: T): Promise<U> =>
|
return (request: T): Promise<U> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
if (queue.length === 0) {
|
|
||||||
setTimeout(_execute, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.push({request, resolve, reject})
|
queue.push({request, resolve, reject})
|
||||||
|
|
||||||
|
if (timeoutId === undefined) {
|
||||||
|
timeoutId = setTimeout(_execute, t)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user