Skip to content

Commit 43c502f

Browse files
committed
Review.
1 parent b3ed5cd commit 43c502f

5 files changed

Lines changed: 90 additions & 117 deletions

File tree

asynciterator.ts

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function setTaskScheduler(scheduler: TaskScheduler): void {
2525
taskScheduler = scheduler;
2626
}
2727

28+
// Returns a function that calls `fn` with `self` as `this` pointer. */
2829
function bind<T extends Function>(fn: T, self?: object): T {
2930
return self ? fn.bind(self) : fn;
3031
}
@@ -537,8 +538,6 @@ export class AsyncIterator<T> extends EventEmitter {
537538
*/
538539
range(start: number, end: number): AsyncIterator<T> {
539540
return this.transform({ offset: start, limit: Math.max(end - start + 1, 0) });
540-
// TODO: Implement as following when .take is optimised
541-
// return this.skip(start).take(Math.max(end - start + 1, 0));
542541
}
543542

544543
/**
@@ -582,7 +581,8 @@ function addSingleListener(source: EventEmitter, eventName: string,
582581
source.on(eventName, listener);
583582
}
584583

585-
function _validateSource<S>(source?: AsyncIterator<S>, allowDestination = false) {
584+
// Validates an AsyncIterator for use as a source within another AsyncIterator
585+
function ensureSourceAvailable<S>(source?: AsyncIterator<S>, allowDestination = false) {
586586
if (!source || !isFunction(source.read) || !isFunction(source.on))
587587
throw new Error(`Invalid source: ${source}`);
588588
if (!allowDestination && (source as any)._destination)
@@ -798,65 +798,66 @@ export class IntegerIterator extends AsyncIterator<number> {
798798
export type MapFunction<S, D = S> = (item: S) => D | null;
799799

800800
/**
801-
An iterator that calls a synchronous mapping function
802-
on every item from its source iterator.
801+
* Function that maps an element to itself.
802+
*/
803+
export function identity<S>(item: S): typeof item {
804+
return item;
805+
}
806+
807+
/**
808+
An iterator that synchronously transforms every item from its source
809+
by applying a mapping function.
803810
@extends module:asynciterator.AsyncIterator
804811
*/
805812
export class MappingIterator<S, D = S> extends AsyncIterator<D> {
806-
private readonly _destroySource: boolean;
807-
protected _source: InternalSource<S>;
813+
protected readonly _map: MapFunction<S, D>;
814+
protected readonly _source: InternalSource<S>;
815+
protected readonly _destroySource: boolean;
808816

809817
/**
810818
* Applies the given mapping to the source iterator.
811819
*/
812820
constructor(
813-
_source: AsyncIterator<S>,
814-
private _map: MapFunction<S, D>,
821+
source: AsyncIterator<S>,
822+
map: MapFunction<S, D> = identity as MapFunction<S, D>,
815823
options: SourcedIteratorOptions = {}
816824
) {
817825
super();
818-
this._source = _validateSource(_source);
826+
this._map = map;
827+
this._source = ensureSourceAvailable(source);
819828
this._destroySource = options.destroySource !== false;
820829

821-
if (_source.done) {
830+
// Close if the source is already empty
831+
if (source.done) {
822832
this.close();
823833
}
834+
// Otherwise, wire up the source for reading
824835
else {
825836
this._source._destination = this;
826837
this._source.on('end', destinationClose);
827838
this._source.on('error', destinationEmitError);
828839
this._source.on('readable', destinationSetReadable);
829-
// If we are given a source that is already readable
830-
// then we need to set the state of this iterable to readable
831-
// also as there is no guarantee that the is no forthcoming
832-
// readable event from the source
833840
this.readable = this._source.readable;
834841
}
835842
}
836843

844+
/** Tries to read the next item from the iterator. */
837845
read(): D | null {
838-
// Do not read the source if the current iterator is ended
839-
if (this.done)
840-
return null;
841-
842-
// A source should only be read from if readable is true
843-
if (!this._source.readable) {
846+
if (!this.done) {
847+
// Try to read an item that maps to a non-null value
848+
if (this._source.readable) {
849+
let item: S | null, mapped: D | null;
850+
while ((item = this._source.read()) !== null) {
851+
if ((mapped = this._map(item)) !== null)
852+
return mapped;
853+
}
854+
}
844855
this.readable = false;
856+
857+
// Close this iterator if the source is empty
845858
if (this._source.done)
846859
this.close();
847-
return null;
848860
}
849-
850-
let item: S | null;
851-
while ((item = this._source.read()) !== null) {
852-
const mapped = this._map(item);
853-
if (mapped !== null)
854-
return mapped;
855-
}
856-
857-
// This will set readable to false on the current iterator
858-
// if the source is no longer readable
859-
this.readable = false;
860861
return null;
861862
}
862863

@@ -1247,7 +1248,7 @@ export class TransformIterator<S, D = S> extends BufferedIterator<D> {
12471248
protected _validateSource(source?: AsyncIterator<S>, allowDestination = false): InternalSource<S> {
12481249
if (this._source || typeof this._createSource !== 'undefined')
12491250
throw new Error('The source cannot be changed after it has been set');
1250-
return _validateSource(source, allowDestination);
1251+
return ensureSourceAvailable(source, allowDestination);
12511252
}
12521253

12531254
/**
@@ -1331,10 +1332,7 @@ export class TransformIterator<S, D = S> extends BufferedIterator<D> {
13311332
}
13321333

13331334
function destinationSetReadable<S>(this: InternalSource<S>) {
1334-
if (this._destination!.readable)
1335-
this._destination!.emit('readable');
1336-
else
1337-
this._destination!.readable = true;
1335+
this._destination!.readable = true;
13381336
}
13391337
function destinationEmitError<S>(this: InternalSource<S>, error: Error) {
13401338
this._destination!.emit('error', error);
@@ -2064,10 +2062,9 @@ export interface BufferedIteratorOptions {
20642062
autoStart?: boolean;
20652063
}
20662064

2067-
export interface TransformIteratorOptions<S> extends BufferedIteratorOptions {
2065+
export interface TransformIteratorOptions<S> extends SourcedIteratorOptions, BufferedIteratorOptions {
20682066
source?: SourceExpression<S>;
20692067
optional?: boolean;
2070-
destroySource?: boolean;
20712068
}
20722069

20732070
export interface TransformOptions<S, D> extends TransformIteratorOptions<S> {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"test:microtask": "npm run mocha",
3232
"test:immediate": "npm run mocha -- --require test/config/useSetImmediate.js",
3333
"mocha": "c8 mocha",
34-
"lint": "eslint asynciterator.ts test",
34+
"lint": "eslint asynciterator.ts test perf",
3535
"docs": "npm run build:module && npm run jsdoc",
3636
"jsdoc": "jsdoc -c jsdoc.json"
3737
},

perf/.eslintrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
rules: {
3+
no-console: off,
4+
},
5+
}
6+

perf/MappingIterator-perf.js

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
import { ArrayIterator, range } from '../dist/asynciterator.js'
1+
import { ArrayIterator, range } from '../dist/asynciterator.js';
22

3-
async function perf(warmupIterator, iterator, description) {
4-
return new Promise((res) => {
5-
// Then run experiment
6-
const now = performance.now()
7-
iterator.on('data', () => { }).on('end', () => {
3+
function noop() {
4+
// empty function to drain an iterator
5+
}
6+
7+
async function perf(warmupIterator, iterator, description) {
8+
return new Promise(res => {
9+
const now = performance.now();
10+
iterator.on('data', noop);
11+
iterator.on('end', () => {
812
console.log(description, performance.now() - now);
913
res();
1014
});
11-
})
15+
});
1216
}
1317

1418
function run(iterator) {
15-
return new Promise((res) => {
16-
iterator.on('data', () => { }).on('end', () => {
19+
return new Promise(res => {
20+
iterator.on('data', noop);
21+
iterator.on('end', () => {
1722
res();
1823
});
19-
})
24+
});
2025
}
2126

2227
function baseIterator() {
@@ -27,28 +32,30 @@ function baseIterator() {
2732
function createMapped(filter) {
2833
let iterator = baseIterator();
2934
for (let j = 0; j < 20; j++) {
30-
iterator = iterator.map((item) => item)
31-
if (filter) {
32-
iterator = iterator.filter((item) => item % (j + 2) === 0)
33-
}
35+
iterator = iterator.map(item => item);
36+
if (filter)
37+
iterator = iterator.filter(item => item % (j + 2) === 0);
3438
}
3539
return iterator;
3640
}
3741

3842
(async () => {
39-
// Warming up
40-
await run(baseIterator());
43+
await run(baseIterator()); // warm-up run
4144

4245
await perf(baseIterator(), createMapped(), '20000000 elems 20 maps\t\t\t\t\t');
4346
await perf(createMapped(true), createMapped(true), '20000000 elems 20 maps 20 filter\t\t\t');
4447

45-
let now = performance.now();
48+
const now = performance.now();
4649
for (let j = 0; j < 100_000; j++) {
4750
let it = range(1, 100);
48-
for (let k = 0; k < 5; k++) {
51+
for (let k = 0; k < 5; k++)
4952
it = it.map(item => item);
50-
}
51-
await new Promise((res) => { it.on('data', (d) => { }).on('end', () => { res() }) })
53+
54+
await new Promise((resolve, reject) => {
55+
it.on('data', () => null);
56+
it.on('end', resolve);
57+
it.on('error', reject);
58+
});
5259
}
5360
console.log('100_000 iterators each with 5 maps and 100 elements\t', performance.now() - now);
5461
})();

0 commit comments

Comments
 (0)