/** * * @param fn an arbitrary callback which performs some operation with side effects. * @returns a tuple: [batchedFn, fireBatch]. * batchedFn takes the same arguments as fn, but the side effects are delayed until fireBatch is called. * if batchedFn is called multiple times, those invocations are stored in order, and then popped off in order when fireBatch is called. */ export function batchify<A extends any[]>(fn: (...args: A)=> void): [((...args: A) => void), () => void] { let batch: A[] = []; return [(...args: A) => { batch.push(args); // console.log({ stack: new Error().stack, batchSize: batch.length }); // console.log({ batchSize: batch.length }); }, (() => { if (batch.length !== 0) { console.log({ fired: batch.length }); } for (let a of batch) { fn(...a); } batch = []; }) ]; } /** * Same use case and types as [batchify], however, specifically we expect [fn] to be a setState function which takes value-or-callback * as its single argument, and instead of calling [fn] repeatedly for each callback in the batch, we apply the callbacks in the batch * sequentially to get a single state update which we then provide to [fn]. */ export function batchifySetState<T>( fn: (arg: T) => void ): [((arg: T) => void), () => void] { let batch: T[] = []; return [(arg: T) => { batch.push(arg); // console.log({ batchSize: batch.length }); }, (() => { if (batch.length === 0) { return; } // console.log({ fired: batch.length }); let thisBatch = [...batch]; batch = []; (fn as any)((prev: any) => { let next = prev; for (let valueOrCallback of thisBatch) { if (typeof valueOrCallback === 'function') { next = valueOrCallback(next); } else { next = valueOrCallback; } } return next; }); })] }