2

Update: I now know that throttle will drop excess function invocations, so it is not the correct tool. I'd still like an idiomatic way to process all items in a queue without going too quickly or dropping any items.


I'm writing a node app that hits an API with a rate limit. I can create calls much faster than I'm allowed to send them. I'd like to consume a queue of calls, but without going too quickly or dropping any of them. I made a small typscript test to illustrate my trouble:

import * as _ from "lodash";

let start = new Date().getTime();

function doLog(s: string) {
  let elapsed = new Date().getTime() - start;
  console.log(`${s} ${elapsed}`);
}

let array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
let throttled = _.throttle(doLog, 100);
array.forEach(s => throttled(s));

I expected to see output roughly like:

a 2
b 101
c 203
d 302
e 405
f 502
g 603
h 706
i 804
j 902

But instead I see:

a 2
j 101

Some odd observations I've made:

  • At 100ms throttle, the size of the array seems irrelevant: I will print the first and last item in the array, whether it has 2 elements or 20.
  • At 1ms throttle, I print 3-6 elements from the front of the array, and the last element
Eric Grunzke
  • 1,487
  • 15
  • 21
  • Yeah, that's not what throttled is meant for. See [the documentation](https://lodash.com/docs#throttle). You can write your own class that does the behaviour you want... I don't think there's anything in lodash to handle that. – David Sherret Jun 01 '16 at 17:18
  • I already did. I also read David Corbacho’s article linked in the docs. The docs say "The throttled function comes with a cancel method to cancel delayed func invocations and a flush method to immediately invoke them." That phrase "delayed func invocations" sure sounds like it's queueing invocations to me... But it sounds like I'm wrong, which is fine. Is there a different function/utility I should be using? – Eric Grunzke Jun 01 '16 at 17:28

3 Answers3

3

If you don't care about doLog() being safely "done" before calling the next one, you can just use setTimeout

let array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
array.forEach((s, i) => setTimeout(doLog, i*100, s));

Adam gives a good explanation as to why throttle isn't good in this case.

fregante
  • 29,050
  • 14
  • 119
  • 159
2

If a throttled function is called more than once during it's wait period, subsequent calls are ignored. If you need to process every item in an array, throttle() probably isn't what you want. It's better suited for preventing excessive updates in the UI, for example.

The reason you're always seeing a and j in your output is because of leading and trailing edges. The whole array was processed in less than 100ms, but since the leading and trailing default to true, you're seeing both of these calls (the first and the last call to the throttled function).

Adam Boduch
  • 11,023
  • 3
  • 30
  • 38
  • Thanks for the clarification about leading/trailing edges; that helps to make sense of the output. Is there an idiomatic solution (or library) for processing each item in an array at a certain rate? – Eric Grunzke Jun 01 '16 at 17:50
  • I'm sure there is. Why do you want to rate-limit array processing, just out of curiosity? – Adam Boduch Jun 01 '16 at 17:59
  • I'm calling an API many times, and that API will reject me if I go too quickly. I'm well under the daily rate, but I can easily spike over the per-second rate. – Eric Grunzke Jun 01 '16 at 18:04
0

If you'd be willing to use RxJS, this answer would let you achieve this fairly idiomatically:

https://stackoverflow.com/a/31855054/553003

Community
  • 1
  • 1
Ptival
  • 9,167
  • 36
  • 53