How do I rate limit requests losslessly using RxJS 5 - javascript

I would like to use make a series of requests to a server, but the server has a hard rate limit of 10 request per second. If I try to make the requests in a loop, it will hit the rate limit since all the requests will happen at the same time.
for(let i = 0; i < 20; i++) {
sendRequest();
}
ReactiveX has lots of tools for modifying observable streams, but I can't seem to find the tools to implement rate limiting. I tried adding a standard delay, but the requests still fire at the same time, just 100ms later than they did previously.
const queueRequest$ = new Rx.Subject<number>();
queueRequest$
.delay(100)
.subscribe(queueData => {
console.log(queueData);
});
const queueRequest = (id) => queueRequest$.next(id);
function fire20Requests() {
for (let i=0; i<20; i++) {
queueRequest(i);
}
}
fire20Requests();
setTimeout(fire20Requests, 1000);
setTimeout(fire20Requests, 5000);
The debounceTime and throttleTime operators are similar to what I'm looking for as well, but that is lossy instead of lossless. I want to preserve every request that I make, instead of discarding the earlier ones.
...
queueRequest$
.debounceTime(100)
.subscribe(queueData => {
sendRequest();
});
...
How do I make these requests to the server without exceeding the rate limit using ReactiveX and Observables?

The implementation in the OP's self answer (and in the linked blog) always imposes a delay which is less than ideal.
If the rate-limited service allows for 10 requests per second, it should be possible to make 10 requests in, say, 10 milliseconds, as long as the next request is not made for another 990 milliseconds.
The implementation below applies a variable delay to ensure the limit is enforced and the delay is only applied to requests that would see the limit exceeded.
function rateLimit(source, count, period) {
return source
.scan((records, value) => {
const now = Date.now();
const since = now - period;
// Keep a record of all values received within the last period.
records = records.filter((record) => record.until > since);
if (records.length >= count) {
// until is the time until which the value should be delayed.
const firstRecord = records[0];
const lastRecord = records[records.length - 1];
const until = firstRecord.until + (period * Math.floor(records.length / count));
// concatMap is used below to guarantee the values are emitted
// in the same order in which they are received, so the delays
// are cumulative. That means the actual delay is the difference
// between the until times.
records.push({
delay: (lastRecord.until < now) ?
(until - now) :
(until - lastRecord.until),
until,
value
});
} else {
records.push({
delay: 0,
until: now,
value
});
}
return records;
}, [])
.concatMap((records) => {
const lastRecord = records[records.length - 1];
const observable = Rx.Observable.of(lastRecord.value);
return lastRecord.delay ? observable.delay(lastRecord.delay) : observable;
});
}
const start = Date.now();
rateLimit(
Rx.Observable.range(1, 30),
10,
1000
).subscribe((value) => console.log(`${value} at T+${Date.now() - start}`));
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>

This blog post does a great job of explaining that RxJS is great at discarding events, and how they came to the answer, but ultimately, the code you're looking for is:
queueRequest$
.concatMap(queueData => Rx.Observable.of(queueData).delay(100))
.subscribe(() => {
sendRequest();
});
concatMap adds concatenates the newly created observable to the back of the observable stream. Additionally, using delay pushes back the event by 100ms, allowing 10 request to happen per second. You can view the full JSBin here, which logs to the console instead of firing requests.

Actually, there's an easier way to do this with the bufferTime() operator and its three arguments:
bufferTime(bufferTimeSpan, bufferCreationInterval, maxBufferSize)
This means we can use bufferTime(1000, null, 10) which means we'll emit a buffer of max 10 items or after max 1s. The null means we want to open a new buffer immediately after the current buffer is emitted.
function mockRequest(val) {
return Observable
.of(val)
.delay(100)
.map(val => 'R' + val);
}
Observable
.range(0, 55)
.concatMap(val => Observable.of(val)
.delay(25) // async source of values
// .delay(175)
)
.bufferTime(1000, null, 10) // collect all items for 1s
.concatMap(buffer => Observable
.from(buffer) // make requests
.delay(1000) // delay this batch by 1s (rate-limit)
.mergeMap(value => mockRequest(value)) // collect results regardless their initial order
.toArray()
)
// .timestamp()
.subscribe(val => console.log(val));
See live demo: https://jsbin.com/mijepam/19/edit?js,console
You can experiment with different initial delay. With only 25ms the request will be sent in batches by 10:
[ 'R0', 'R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8', 'R9' ]
[ 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'R16', 'R17', 'R18', 'R19' ]
[ 'R20', 'R21', 'R22', 'R23', 'R24', 'R25', 'R26', 'R27', 'R28', 'R29' ]
[ 'R30', 'R31', 'R32', 'R33', 'R34', 'R35', 'R36', 'R37', 'R38', 'R39' ]
[ 'R40', 'R41', 'R42', 'R43', 'R44', 'R45', 'R46', 'R47', 'R48', 'R49' ]
[ 'R50', 'R51', 'R52', 'R53', 'R54' ]
But with .delay(175) we'll emit batches of less than 10 items because we're limited by the 1s delay.
[ 'R0', 'R1', 'R2', 'R3', 'R4' ]
[ 'R5', 'R6', 'R7', 'R8', 'R9', 'R10' ]
[ 'R11', 'R12', 'R13', 'R14', 'R15' ]
[ 'R16', 'R17', 'R18', 'R19', 'R20', 'R21' ]
[ 'R22', 'R23', 'R24', 'R25', 'R26', 'R27' ]
[ 'R28', 'R29', 'R30', 'R31', 'R32' ]
[ 'R33', 'R34', 'R35', 'R36', 'R37', 'R38' ]
[ 'R39', 'R40', 'R41', 'R42', 'R43' ]
[ 'R44', 'R45', 'R46', 'R47', 'R48', 'R49' ]
[ 'R50', 'R51', 'R52', 'R53', 'R54' ]
There's however one difference to what you might need. This solution starts initially starts emitting values after 2s delay because of the .bufferTime(1000, ...) and delay(1000). All other emissions happen after 1s.
You could eventually use:
.bufferTime(1000, null, 10)
.mergeAll()
.bufferCount(10)
This will always collect 10 items and only after that it'll perform the request. This would be probably more efficient.

I wrote a library to do this, you set up the maximum number of requests per interval and it rate limits observables by delaying subscriptions. It's tested and with examples: https://github.com/ohjames/rxjs-ratelimiter

Go with Adam’s answer. However, bear in mind the traditional of().delay() will actually add a delay before every element. In particular, this will delay the first element of your observable, as well as any element that wasn’t actually rate limited.
Solution
You can work around this by having your concatMap return a stream of observables that immediately emit a value, but only complete after a given delay:
new Observable(sub => {
sub.next(v);
setTimeout(() => sub.complete(), delay);
})
This is kind of a mouthful, so I’d create a function for it. That said, since there’s no use for this outside of actual rate limiting, you’d probably be better served just writing a rateLimit operator:
function rateLimit<T>(
delay: number,
scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction<T> {
return concatMap(v => new Observable(sub => {
sub.next(v);
scheduler.schedule(() => sub.complete(), delay);
}));
}
Then:
queueRequest$.pipe(
rateLimit(100),
).subscribe(...);
Limitation
This will now create a delay after every element. This means that if your source observable emits its last value then completes, your resulting rate-limited observable will have a little delay between itself between its last value, and completing.

Updated cartant's answer as pipe-able operator for newer rxjs versions:
function rateLimit(count: number, period: number) {
return <ValueType>(source: Observable<ValueType>) => {
return source.pipe
( scan((records, value) => {
let now = Date.now();
let since = now - period;
// Keep a record of all values received within the last period.
records = records.filter((record) => record.until > since);
if (records.length >= count) {
// until is the time until which the value should be delayed.
let firstRecord = records[0];
let lastRecord = records[records.length - 1];
let until = firstRecord.until + (period * Math.floor(records.length / count));
// concatMap is used below to guarantee the values are emitted
// in the same order in which they are received, so the delays
// are cumulative. That means the actual delay is the difference
// between the until times.
records.push(
{ delay: (lastRecord.until < now) ?
(until - now) :
(until - lastRecord.until)
, until
, value });
} else {
records.push(
{ delay: 0
, until: now
, value });
}
return records;
}, [] as RateLimitRecord<ValueType>[])
, concatMap((records) => {
let lastRecord = records[records.length - 1];
let observable = of(lastRecord.value);
return lastRecord.delay ? observable.pipe(delay(lastRecord.delay)) : observable;
}) );
};
}
interface RateLimitRecord<ValueType> {
delay: number;
until: number;
value: ValueType;
}

Related

What is the simplest way of giving Tone.js arrays of notes and durations in seconds to play back?

I would like to give Tone.js a list of notes and corresponding durations for each note and have it play back the sequence. As far as I can see, there is no easy way to do this.
In the following, the corresponding time values are not the ones I entered (i.e 0.25, 0.5, 0.25), as evidenced by the console.log:
var part = new Tone.Part(function(time, note){
console.log(time);
console.log(note);
synth.triggerAttackRelease(note, time);
}, [[0.25, "C2"], [0.5, "C3"], [0.25, "G2"]]);
part.start(0).loop = false;
Tone.Transport.start();
How can I give Tone.js notes and corresponding ms for playback?
I'm not familiar with Tone.js, so there's probably a better way of doing this. The official example for the array shorthand that you're using doesn't seem to work, so it might be a library issue.
As for what you're trying to achieve, I fiddled with it out of curiosity and here's what I've come to:
function timeFromDurations(value, i, arr) {
const prevTime = arr[i - 1]?.time;
value.time = prevTime + arr[i - 1]?.duration || 0;
return value;
}
const notesAndDurations = [
{ note: 'C3', duration: .25 },
{ note: 'C4', duration: .5 },
{ note: 'G2', duration: 1 },
].map(timeFromDurations);
console.log(notesAndDurations);
const synth = new Tone.Synth().toDestination();
// use an array of objects as long as the object has a "time" attribute
const part = new Tone.Part((time, value) => {
// the value is an object which contains both the note and the velocity
synth.triggerAttackRelease(value.note, value.duration, time);
}, notesAndDurations).start(0);
Tone.Transport.start();
The idea is that you need to set start time of each note based on previous note start time + duration. That removes the need to set the start time(not optional) manually.
Edit
For your second case where the durations and the notes come in separate arrays you can use the following reduce function:
const notes = ['C3', 'C4', 'G2'];
const durations = [0.25, 0.5, 1];
const noteDurationTime = notes.reduce((acc, note, i) => {
const prevTime = acc[i - 1]?.time;
const time = prevTime + acc[i - 1]?.duration || 0;
const duration = durations[i];
acc.push({ note, duration, time });
return acc;
}, []);
The idea is the same, you're building an array of objects that have all the needed properties(note, duration, time), but this time from different sources(notes array and durations array).
You want to make sure that both these arrays are the same length.

How do I accurately setState on a time delay?

I am trying to setState on a given time interval (100ms). I have discovered both in practice and through research that setInterval is inaccurate, and will float over time. I need it to be accurate because I'm aiming to have data (from an array) in my app synced up to a video.
Here is what I currently have:
data = this.processCSV(text);
let idx = 0
this.intOne = setInterval(() => {
this.setState(previousState => (
{
data1: parseFloat(data[idx][2]),
data2: parseFloat(data[idx][1]),
data3: parseFloat(data[idx][3])
}
));
idx = idx + 1;
}, 100);
In my research, I've found a few solutions that use Date to accurately keep track of time and account for drifting.
Expected Results: setState is called 10 times per second for an extended amount of time
Current Results: setState is called ~9 times per second and continues to float away from expected time

RxJS mix combineLatest and zip

I have two sources of streams that I want to listen to them. The requirements are:
If one emits give me also the last value from the second.
If two of them emits at the same time, don't call the subscribe two times.
The first case is combineLatest, but the second is zip. I need a way to mix combineLatest and zip into one operator.
const { Observable, BehaviorSubject} = Rx;
const movies = {
ids: [],
entities: {}
}
const actors = {
ids: [],
entities: {}
}
const storeOne = new BehaviorSubject(movies);
const storeTwo = new BehaviorSubject(actors);
const movies$ = storeOne.map(state => state.entities).distinctUntilChanged();
const actors$ = storeTwo.map(state => state.entities).distinctUntilChanged();
const both$ = Observable.zip(
movies$,
actors$,
(movies, actors) => {
return {movies, actors};
}
)
both$.subscribe(console.log);
storeOne.next({
...storeOne.getValue(),
entities: {
1: {id: 1}
},
ids: [1]
});
storeTwo.next({
...storeTwo.getValue(),
entities: {
1: {id: 1}
},
ids: [1]
});
The above code works fine when both emits one after the other, but I need to support also a case where one of them emits. (combineLatest)
How can I do that?
Yes, as advised by #cartant you can use Observable.combineLatest(movies$, actors$, (movies, actors) => ({ movies, actors })).auditTime(0)
To elaborate the above,
auditTime(n) will wait for n milliseconds and emit the latest value.
auditTime(0) is similar to setTimeout(0), it actually waits for nothing (executes immediately), but waits for the current event/execution loop to complete.
Here values B & 2 are emitted together, so when you use combineLatest you would get either B1,B2 or A2, B2 (which is based on the internal clock). Regardless B2 is the latest value in that execution loop. Since we are waiting for 0 milliseconds i.e. for the current execution loop to get the latest value via auditTime(0), the observable would emit only B2.

Are Java's Streams like JavaScript's Arrays? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I try to build the Javascript equvivalent for Java's IntStream.range(0, 5).forEach(System.err::println); and reached
const IntStream = (function () {
function range(start, end, numbers = []) {
if (start === end) {
return numbers
}
return range(start + 1, end, numbers.concat(start))
}
return {
range
}
})()
IntStream.range(0, 5).forEach(number => console.log(number))
All the stream magic of Java is builtin in a normal JavaScript array. Why can't an ArrayList in Java do all same things as a Stream or is there a purpose I didn't figure out yet?
Array higher order functions will eagerly do the whole thing at each step.
const isOdd = v => v % 2 == 1;
const multiply = by => v => v * by;
const arrRange = IntStream.range(10, 20);
const arrOdd = arrRange.filter(isOdd);
const arrOddM3 = arrOdd.map(multiply(3));
Here all the bindings are distinct arrays created by each of the steps. Even when you chain them the intermediate arrays are always made and the whole array at each step need to be finished before the next can begin.
const arrOddM3 = IntStream.range(10, 20).filter(isOdd).map(multiply(3));
arrOddM3; // ==> [33, 39, 45, 51, 57]
Streams are different since they only compute values when they are accessed. A stream version would look very similar.
const streamOddM3 = Stream.range(10, Infinity).filter(isOdd).map(multiply(3));
streamOddM3; // ==> Stream
Notice I have changed the end to go to infinity. I can do that because at most it calculates the very first value and some implementations doesn't do any calculations at all until you ask for the values. To force the calculations you can take some values and get them returned as an array:
streamOddM3.take(3); // ==> [33, 39, 45]
Here is a Stream implementation loosely based on the one from the SICP videos which work similar to Java's streams.
class EmptyStream {
map() {
return this;
}
filter() {
return this;
}
take() {
return [];
}
}
class Stream extends EmptyStream {
constructor(value, next) {
super();
this._next = next;
this.value = value;
}
/**
* This prevents the value to be computed more than once
* #returns {EmptyStream|Stream}
*/
next() {
if( ! (this._next instanceof EmptyStream) ) {
this._next = this._next();
}
return this._next;
}
map(fn) {
return new Stream(fn(this.value), () => this.next().map(fn));
}
filter(fn) {
return fn(this.value) ?
new Stream(this.value, () => this.next().filter(fn)) :
this.next().filter(fn);
}
take(n) {
return n == 0 ? [] : [this.value, ...this.next().take(n && n - 1)];
}
static range(from, to, step = 1) {
if (to !== undefined && ( step > 0 && from > to || step < 0 && from < to )) {
return Stream.emptyStream;
}
return new Stream(from, () => Stream.range(from + step, to, step));
}
}
Stream.emptyStream = new EmptyStream();
There are alternatives to Stream that might work in their place.
In JavaScript you have generators (aka coroutines) and you can make a map and filter generator function that takes a generator source and becomes a new generator with that transformation. Since it is already in the language it might be a better match than Streams but I haven't studied it enough to make a generator example of the above.
In Clojure you have transducers that allows you to compose steps so that an eventual list making only happens for the elements that makes it to the final result. They are easily implemented in JavaScript.
Theres a big difference between Streams and Javasvript arrays:
[1,2,3,4]
.filter(el => {
console.log(el);
return el%2 === 0;
})
.forEach( el => console.log(el));
The result in javascript will be:
1,2,3,4 2,4
for a Stream it will be:
1,2 2 3,4 4
So as you can see javascript mutates the collection, then iterates the collection. An element passed into a Stream traverses the stream. If a collection is passed to a Stream, one element after another will be passed in the stream.
A possible Stream implementation would be:
class Stream {
constructor(){
this.queue = [];
}
//the modifying methods
forEach(func){
this.queue.push(["forEach",func]);
return this;
}
filter(func){
this.queue.push(["filter",func]);
return this;
}
map(func){
this.queue.push(["map",func]);
return this;
}
subStream(v){
this.forEach(d => v.get(d));
return this;
}
//data methods
get(value,cb){
for( let [type,func] of this.queue ){
switch(type){
case "forEach":
func(value);
break;
case "map":
value = func(value);
break;
case "filter":
if(! func(value)) return;
}
}
cb(value);
}
range(start,end){
const result = [];
Array.from({length:end-start})
.forEach((_,i)=> this.get(i+start, r => result.push(r)));
return result;
}
}
Usecase:
const nums = new Stream();
const even = new Stream();
even.filter(n => !(n%2) ).forEach(n => console.log(n));
const odd = new Stream();
even.filter(n => (n%2) ).forEach(n => console.log(n));
nums
.subStream(even)
.subStream(odd)
.range(0,100);
No they are not the same because of how they proccess the data.
In LINQ (C#) or javascript, each operation on a collection must end befor calling to the next operation in the pipeline.
In streams, its different. For example:
Arrays.asList(1,2,3).stream()
.filter((Integer x)-> x>1)
.map((Integer x)->x*10)
.forEach(System.out::println);
source collection: 1, 2 ,3
filter(1) -> You are not OK. Element 1 will not pass to the next operation
in the pipeline. Now deal with element 2.
filter(2) -> You are OK. element 2 pass to the next operation.
map(2) -> create new element 20 and put it in the new stream.
forEach(20) -> print 20. End dealing with element 2 in the source collection.
Now deal with element 3.
filter(3) -> You are OK. element 3 pass to the next operation
map(3) -> create new element 30 and put it in the new stream.
forEach(20) -> print 30. No more elements in the source collection.
finish excuting the stream.
output:
20
30
Illustration:
One of the outcome of this approach is sometimes some operations in the pipeline won't go over each element because some of them filtered out in the proccess.
This explanation were taken from: Streams In Depth By Stav Alfi

Rate-limiting and count-limiting events in RxJS v5, but also allowing pass-through

I have a bunch of events to send up to a service. But the requests are rate limited and each request has a count limit:
1 request per second: bufferTime(1000)
100 event items per request: bufferCount(100)
The problem is, I am not sure how to combine them in a way that makes sense.
Allowing pass-through
Complicating this further, I need to make sure that events go through instantaneously if we don't hit either limit.
For example, I don't want it to actually wait for 100 event items before letting it go through if it's only one single event during a non-busy time.
Legacy API
I also found that there was a bufferWithTimeOrCount that existed in RxJS v4, although I am not sure how I'd use that even if I had it.
Test playground
Here is a JSBin I made for you to test your solution:
http://jsbin.com/fozexehiba/1/edit?js,console,output
Any help would be greatly appreciated.
The bufferTime() operator takes three parameters which combines the functionality of bufferTime and bufferCount. See http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-bufferTime.
With .bufferTime(1000, null, 3) you can make a buffer every 1000ms or when it reaches 3 items. However, this means that it doesn't guarantee 1000ms delay between each buffer.
So you could use something like this which is pretty easy to use (buffers only 3 items for max 1000ms):
click$
.scan((a, b) => a + 1, 0)
.bufferTime(1000, null, 3)
.filter(buffer => buffer.length > 0)
.concatMap(buffer => Rx.Observable.of(buffer).delay(1000))
.timestamp()
.subscribe(console.log);
See live demo: http://jsbin.com/libazer/7/edit?js,console,output
The only difference to what you probably wanted is that the first emission might be delayed by more than 1000ms. This is because both bufferTime() and delay(1000) operators make a delay to ensure that there's always at least 1000ms gap.
I hope this works for you.
Operator
events$
.windowCount(10)
.mergeMap(m => m.bufferTime(100))
.concatMap(val => Rx.Observable.of(val).delay(100))
.filter(f => f.length > 0)
Doc
.windowCount(number) : [ Rx Doc ]
.bufferTime(number) : [ Rx Doc ]
Demo
// test case
const mock = [8, 0, 2, 3, 30, 5, 6, 2, 2, 0, 0, 0, 1]
const tInterval = 100
const tCount = 10
Rx.Observable.interval(tInterval)
.take(mock.length)
.mergeMap(mm => Rx.Observable.range(0, mock[mm]))
// start
.windowCount(tCount)
.mergeMap(m => m.bufferTime(tInterval))
.concatMap(val => Rx.Observable.of(val).delay(tInterval))
.filter(f => f.length > 0)
// end
.subscribe({
next: (n) => console.log('Next: ', n),
error: (e) => console.log('Error: ', e),
complete: (c) => console.log('Completed'),
})
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
Updated
After more testing. I found the answer above has some problem in extreme condition. I think they are caused by .window() and .concat(), and then I find a warning in the doc#concatMap.
Warning: if source values arrive endlessly and faster than their corresponding inner Observables can complete, it will result in memory issues as inner Observables amass in an unbounded buffer waiting for their turn to be subscribed to.
However, I thought the right way to limit the request rate possibly is, that we could limit the cycle time of requests. In your case, just limit there is only 1 request per 10 milliseconds. It is simpler and may be more efficient to control the requests.
Operator
const tInterval = 100
const tCount = 10
const tCircle = tInterval / tCount
const rxTimer = Rx.Observable.timer(tCircle).ignoreElements()
events$
.concatMap(m => Rx.Observable.of(m).merge(rxTimer)) // more accurate than `.delay()`
// .concatMap(m => Rx.Observable.of(m).delay(tCircle))
or
events$
.zip(Rx.Observable.interval(tCircle), (x,y) => x)
I've modified the answer I gave to this question to support your use case of adding a limited number of values (i.e. events) to pending requests.
The comments within should explain how it works.
Because you need to keep a record of the requests that have been made within the rate limit period, I don't believe that it's possible to use the bufferTime and bufferCount operators to do what you want - a scan is required so that you can maintain that state within the observable.
function rateLimit(source, period, valuesPerRequest, requestsPerPeriod = 1) {
return source
.scan((requests, value) => {
const now = Date.now();
const since = now - period;
// Keep a record of all requests made within the last period. If the
// number of requests made is below the limit, the value can be
// included in an immediate request. Otherwise, it will need to be
// included in a delayed request.
requests = requests.filter((request) => request.until > since);
if (requests.length >= requestsPerPeriod) {
const leastRecentRequest = requests[0];
const mostRecentRequest = requests[requests.length - 1];
// If there is a request that has not yet been made, append the
// value to that request if the number of values in that request's
// is below the limit. Otherwise, another delayed request will be
// required.
if (
(mostRecentRequest.until > now) &&
(mostRecentRequest.values.length < valuesPerRequest)
) {
mostRecentRequest.values.push(value);
} else {
// until is the time until which the value should be delayed.
const until = leastRecentRequest.until + (
period * Math.floor(requests.length / requestsPerPeriod)
);
// concatMap is used below to guarantee the values are emitted
// in the same order in which they are received, so the delays
// are cumulative. That means the actual delay is the difference
// between the until times.
requests.push({
delay: (mostRecentRequest.until < now) ?
(until - now) :
(until - mostRecentRequest.until),
until,
values: [value]
});
}
} else {
requests.push({
delay: 0,
until: now,
values: [value]
});
}
return requests;
}, [])
// Emit only the most recent request.
.map((requests) => requests[requests.length - 1])
// If multiple values are added to the request, it will be emitted
// mulitple times. Use distinctUntilChanged so that concatMap receives
// the request only once.
.distinctUntilChanged()
.concatMap((request) => {
const observable = Rx.Observable.of(request.values);
return request.delay ? observable.delay(request.delay) : observable;
});
}
const start = Date.now();
rateLimit(
Rx.Observable.range(1, 250),
1000,
100,
1
).subscribe((values) => console.log(
`Request with ${values.length} value(s) at T+${Date.now() - start}`
));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>

Categories