-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fea(asynciterable-operators): Add
bufferCountOrTime
operator (#324)
- Loading branch information
Showing
5 changed files
with
110 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Buffer - count or time | ||
|
||
An operator that's useful when you need to handle messages in batches. | ||
Let's say you have an ongoing subscription to something but want to handle batches of messages. | ||
|
||
```ts | ||
await subscription.pipe( | ||
// emit when buffer hits 16 items, or every 100ms | ||
bufferCountOrTime(16, 100) | ||
) | ||
.forEach(handleBatch) | ||
``` | ||
|
||
Using this operator makes sure that if messages slow down you'll still | ||
handle them in a reasonable time whereas using `buffer` would leave you stuck until you get | ||
the right amount of messages. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import '../asynciterablehelpers'; | ||
import { of, concat } from 'ix/asynciterable'; | ||
import { bufferCountOrTime, delay } from 'ix/asynciterable/operators'; | ||
|
||
test('buffer count behaviour', async () => { | ||
const result: number[][] = []; | ||
|
||
await of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) | ||
.pipe(bufferCountOrTime(5, 10)) | ||
.forEach((buf) => { | ||
result.push(buf); | ||
}); | ||
|
||
expect(result).toEqual([ | ||
[1, 2, 3, 4, 5], | ||
[6, 7, 8, 9, 0], | ||
]); | ||
}); | ||
|
||
test('buffer time behaviour', async () => { | ||
const result: number[][] = []; | ||
const seq = concat(of(1, 2, 3, 4, 5), of(6, 7, 8, 9), of(0).pipe(delay(11))); | ||
await seq.pipe(bufferCountOrTime(5, 10)).forEach((buf) => { | ||
result.push(buf); | ||
}); | ||
|
||
expect(result).toEqual([[1, 2, 3, 4, 5], [6, 7, 8, 9], [0]]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { OperatorAsyncFunction } from '../../interfaces'; | ||
import { AsyncIterableX, interval, concat, of } from '../'; | ||
import { map } from './map'; | ||
import { merge } from '../merge'; | ||
import { wrapWithAbort } from './withabort'; | ||
|
||
const timerEvent = {}; | ||
const ended = {}; | ||
|
||
class BufferCountOrTime<TSource> extends AsyncIterableX<TSource[]> { | ||
constructor( | ||
private readonly source: AsyncIterable<TSource>, | ||
private readonly bufferSize: number, | ||
private readonly maxWaitTime: number | ||
) { | ||
super(); | ||
} | ||
|
||
async *[Symbol.asyncIterator](signal?: AbortSignal) { | ||
const buffer: TSource[] = []; | ||
const timer = interval(this.maxWaitTime).pipe(map(() => timerEvent)); | ||
const source = concat(this.source, of(ended)); | ||
const merged = merge(source, timer); | ||
|
||
for await (const item of wrapWithAbort(merged, signal)) { | ||
if (item === ended) { | ||
break; | ||
} | ||
if (item !== timerEvent) { | ||
buffer.push(item as TSource); | ||
} | ||
if (buffer.length >= this.bufferSize || (buffer.length && item === timerEvent)) { | ||
yield buffer.slice(); | ||
buffer.length = 0; | ||
} | ||
} | ||
|
||
if (buffer.length) { | ||
yield buffer; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Projects each element of an async-iterable sequence into consecutive buffers | ||
* which are emitted when either the threshold count or time is met. | ||
* | ||
* @export | ||
* @template TSource The type of elements in the source sequence. | ||
* @param {number} count The size of the buffer. | ||
* @param {number} time The threshold number of milliseconds to wait before flushing a non-full buffer | ||
* @returns {OperatorAsyncFunction<TSource, TSource[]>} An operator which returns an async-iterable sequence | ||
* of buffers | ||
*/ | ||
export function bufferCountOrTime<TSource>( | ||
count: number, | ||
time: number | ||
): OperatorAsyncFunction<TSource, TSource[]> { | ||
return function bufferOperatorFunction( | ||
source: AsyncIterable<TSource> | ||
): AsyncIterableX<TSource[]> { | ||
return new BufferCountOrTime<TSource>(source, count, time); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters