@@ -70,45 +70,86 @@ export interface ThrottledFunction<T extends Array<unknown>> {
7070 * assert(func.lastExecution > 0);
7171 * ```
7272 *
73+ * @example With dynamic timeframe
74+ *
75+ * ```ts no-assert
76+ * import { throttle } from "@std/async/unstable-throttle";
77+ *
78+ * function processUserInput(input: string) {
79+ * // Do some expensive computation with user input that changes on each
80+ * // keypress, which takes a variable amount of time depending on the length
81+ * // or complexity of input.
82+ * }
83+ *
84+ * const processUserInputThrottled = throttle(
85+ * processUserInput,
86+ * // Throttle dynamically, waiting twice as long as the previous execution
87+ * // took to complete before starting the next call.
88+ * (n) => n * 2,
89+ * { ensureLastCall: true },
90+ * );
91+ * ```
92+ *
7393 * @typeParam T The arguments of the provided function.
7494 * @param fn The function to throttle.
7595 * @param timeframe The timeframe in milliseconds in which the function should be called at most once.
96+ * If a callback function is supplied, it will be called with the duration of
97+ * the previous execution and should return the
98+ * next timeframe to use in milliseconds.
7699 * @param options Additional options.
77100 * @returns The throttled function.
78101 */
79102// deno-lint-ignore no-explicit-any
80103export function throttle < T extends Array < any > > (
81104 fn : ( this : ThrottledFunction < T > , ...args : T ) => void ,
82- timeframe : number ,
105+ timeframe : number | ( ( previousDuration : number ) => number ) ,
83106 options ?: ThrottleOptions ,
84107) : ThrottledFunction < T > {
85108 const ensureLast = Boolean ( options ?. ensureLastCall ) ;
86109 let timeout = - 1 ;
87110
88111 let lastExecution = - Infinity ;
89112 let flush : ( ( ) => void ) | null = null ;
113+ let throttlingAsync = false ;
114+
115+ let tf = typeof timeframe === "function" ? 0 : timeframe ;
90116
91117 const throttled = ( ( ...args : T ) => {
92118 flush = ( ) => {
119+ const start = Date . now ( ) ;
120+ let result : unknown ;
121+ const done = ( ) => {
122+ throttlingAsync = false ;
123+ lastExecution = Date . now ( ) ;
124+ if ( typeof timeframe === "function" ) {
125+ tf = timeframe ( lastExecution - start ) ;
126+ }
127+ } ;
93128 try {
94129 clearTimeout ( timeout ) ;
95- fn . call ( throttled , ...args ) ;
130+ result = fn . call ( throttled , ...args ) ;
96131 } finally {
97- lastExecution = Date . now ( ) ;
132+ if ( isPromiseLike ( result ) ) {
133+ throttlingAsync = true ;
134+ Promise . resolve ( result ) . finally ( done ) ;
135+ } else {
136+ done ( ) ;
137+ }
98138 flush = null ;
99139 }
100140 } ;
101141 if ( throttled . throttling ) {
102142 if ( ensureLast ) {
103143 clearTimeout ( timeout ) ;
104- timeout = setTimeout ( ( ) => flush ?.( ) , timeframe ) ;
144+ timeout = setTimeout ( ( ) => flush ?.( ) , tf ) ;
105145 }
106146 return ;
107147 }
108148 flush ?.( ) ;
109149 } ) as ThrottledFunction < T > ;
110150
111151 throttled . clear = ( ) => {
152+ throttlingAsync = false ;
112153 lastExecution = - Infinity ;
113154 } ;
114155
@@ -117,9 +158,15 @@ export function throttle<T extends Array<any>>(
117158 } ;
118159
119160 Object . defineProperties ( throttled , {
120- throttling : { get : ( ) => Date . now ( ) - lastExecution <= timeframe } ,
161+ throttling : {
162+ get : ( ) => Date . now ( ) - lastExecution <= tf || throttlingAsync ,
163+ } ,
121164 lastExecution : { get : ( ) => lastExecution } ,
122165 } ) ;
123166
124167 return throttled ;
125168}
169+
170+ function isPromiseLike ( obj : unknown ) : obj is PromiseLike < unknown > {
171+ return typeof ( obj as PromiseLike < unknown > ) ?. then === "function" ;
172+ }
0 commit comments