sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
98 lines (89 loc) • 2.85 kB
text/typescript
import {Observable, type Subscriber} from 'rxjs'
import {isNonNullable} from '../../util'
interface QueueItem {
args: any[]
observer: Subscriber<any>
completed: boolean
}
// Takes an observable returning function and returns a debounced function that, when called
// collects its passed arguments until wait time has passed without receiving new calls.
// When wait period is over, calls the original function with the collected arguments
export function debounceCollect<Fn extends (...args: any[]) => Observable<any[]>>(
fn: Fn,
wait: number,
): Fn extends (collectedArgs: [...infer TArgs][]) => Observable<(infer TReturnValue)[]>
? (...args: TArgs) => Observable<TReturnValue>
: never
export function debounceCollect(fn: any, wait: number) {
let timer: ReturnType<typeof setTimeout>
let queue: Record<number | string, QueueItem | undefined> = {}
let idx = 0
return function debounced(...args: any[]) {
return new Observable((obs) => {
clearTimeout(timer)
timer = setTimeout(flush, wait)
const queueItem: QueueItem = {
args: args,
observer: obs,
completed: false,
}
const id = idx++
queue[id] = queueItem
return () => {
// console.log('completed', queueItem.args)
queueItem.completed = true
}
})
}
function flush() {
const currentlyFlushingQueue = queue
queue = {}
const queueItemIds = Object.keys(currentlyFlushingQueue) // Todo: use debug
// .map(id => {
// if (currentlyFlushingQueue[id].completed) {
// console.log('Dropped', currentlyFlushingQueue[id].args)
// }
// return id
// })
.filter((id) => {
const queueItem = currentlyFlushingQueue[id]
return queueItem && !queueItem.completed
})
if (queueItemIds.length === 0) {
// nothing to do
return
}
const collectedArgs = queueItemIds
.map((id) => {
const queueItem = currentlyFlushingQueue[id]
return queueItem && queueItem.args
})
.filter(isNonNullable)
fn(collectedArgs).subscribe({
next(results: any[]) {
results.forEach((result, i) => {
const queueItem = currentlyFlushingQueue[queueItemIds[i]]
if (queueItem && !queueItem.completed) {
queueItem.observer.next(results[i])
}
})
},
complete() {
queueItemIds.forEach((id) => {
const entry = currentlyFlushingQueue[id]
if (entry && !entry.completed) {
entry.observer.complete()
}
})
},
error(err: Error) {
queueItemIds.forEach((id) => {
const entry = currentlyFlushingQueue[id]
if (entry && !entry.completed) {
entry.observer.error(err)
}
})
},
})
}
}