Skip to content

Commit dca60f8

Browse files
authored
Merge pull request #3 from projectfluent/from
Add CachedIterable.from
2 parents e3abc35 + b5eedbb commit dca60f8

5 files changed

+90
-39
lines changed

src/cached_async_iterable.mjs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1+
import CachedIterable from "./cached_iterable.mjs";
2+
13
/*
24
* CachedAsyncIterable caches the elements yielded by an async iterable.
35
*
46
* It can be used to iterate over an iterable many times without depleting the
57
* iterable.
68
*/
7-
export default class CachedAsyncIterable {
9+
export default class CachedAsyncIterable extends CachedIterable {
810
/**
911
* Create an `CachedAsyncIterable` instance.
1012
*
1113
* @param {Iterable} iterable
1214
* @returns {CachedAsyncIterable}
1315
*/
1416
constructor(iterable) {
17+
super();
18+
1519
if (Symbol.asyncIterator in Object(iterable)) {
1620
this.iterator = iterable[Symbol.asyncIterator]();
1721
} else if (Symbol.iterator in Object(iterable)) {
1822
this.iterator = iterable[Symbol.iterator]();
1923
} else {
2024
throw new TypeError("Argument must implement the iteration protocol.");
2125
}
22-
23-
this.seen = [];
2426
}
2527

2628
/**
@@ -30,15 +32,15 @@ export default class CachedAsyncIterable {
3032
* cached elements of the original (async or sync) iterable.
3133
*/
3234
[Symbol.iterator]() {
33-
const {seen} = this;
35+
const cached = this;
3436
let cur = 0;
3537

3638
return {
3739
next() {
38-
if (seen.length === cur) {
40+
if (cached.length === cur) {
3941
return {value: undefined, done: true};
4042
}
41-
return seen[cur++];
43+
return cached[cur++];
4244
}
4345
};
4446
}
@@ -52,15 +54,15 @@ export default class CachedAsyncIterable {
5254
* iterable.
5355
*/
5456
[Symbol.asyncIterator]() {
55-
const { seen, iterator } = this;
57+
const cached = this;
5658
let cur = 0;
5759

5860
return {
5961
async next() {
60-
if (seen.length <= cur) {
61-
seen.push(await iterator.next());
62+
if (cached.length <= cur) {
63+
cached.push(await cached.iterator.next());
6264
}
63-
return seen[cur++];
65+
return cached[cur++];
6466
}
6567
};
6668
}
@@ -72,15 +74,14 @@ export default class CachedAsyncIterable {
7274
* @param {number} count - number of elements to consume
7375
*/
7476
async touchNext(count = 1) {
75-
const { seen, iterator } = this;
7677
let idx = 0;
7778
while (idx++ < count) {
78-
if (seen.length === 0 || seen[seen.length - 1].done === false) {
79-
seen.push(await iterator.next());
79+
if (this.length === 0 || this[this.length - 1].done === false) {
80+
this.push(await this.iterator.next());
8081
}
8182
}
8283
// Return the last cached {value, done} object to allow the calling
8384
// code to decide if it needs to call touchNext again.
84-
return seen[seen.length - 1];
85+
return this[this.length - 1];
8586
}
8687
}

src/cached_iterable.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Base CachedIterable class.
3+
*/
4+
export default class CachedIterable extends Array {
5+
/**
6+
* Create a `CachedIterable` instance from an iterable or, if another
7+
* instance of `CachedIterable` is passed, return it without any
8+
* modifications.
9+
*
10+
* @param {Iterable} iterable
11+
* @returns {CachedIterable}
12+
*/
13+
static from(iterable) {
14+
if (iterable instanceof this) {
15+
return iterable;
16+
}
17+
18+
return new this(iterable);
19+
}
20+
}

src/cached_sync_iterable.mjs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
1+
import CachedIterable from "./cached_iterable.mjs";
2+
13
/*
24
* CachedSyncIterable caches the elements yielded by an iterable.
35
*
46
* It can be used to iterate over an iterable many times without depleting the
57
* iterable.
68
*/
7-
export default class CachedSyncIterable {
9+
export default class CachedSyncIterable extends CachedIterable {
810
/**
911
* Create an `CachedSyncIterable` instance.
1012
*
1113
* @param {Iterable} iterable
1214
* @returns {CachedSyncIterable}
1315
*/
1416
constructor(iterable) {
17+
super();
18+
1519
if (Symbol.iterator in Object(iterable)) {
1620
this.iterator = iterable[Symbol.iterator]();
1721
} else {
1822
throw new TypeError("Argument must implement the iteration protocol.");
1923
}
20-
21-
this.seen = [];
2224
}
2325

2426
[Symbol.iterator]() {
25-
const { seen, iterator } = this;
27+
const cached = this;
2628
let cur = 0;
2729

2830
return {
2931
next() {
30-
if (seen.length <= cur) {
31-
seen.push(iterator.next());
32+
if (cached.length <= cur) {
33+
cached.push(cached.iterator.next());
3234
}
33-
return seen[cur++];
35+
return cached[cur++];
3436
}
3537
};
3638
}
@@ -42,15 +44,14 @@ export default class CachedSyncIterable {
4244
* @param {number} count - number of elements to consume
4345
*/
4446
touchNext(count = 1) {
45-
const { seen, iterator } = this;
4647
let idx = 0;
4748
while (idx++ < count) {
48-
if (seen.length === 0 || seen[seen.length - 1].done === false) {
49-
seen.push(iterator.next());
49+
if (this.length === 0 || this[this.length - 1].done === false) {
50+
this.push(this.iterator.next());
5051
}
5152
}
5253
// Return the last cached {value, done} object to allow the calling
5354
// code to decide if it needs to call touchNext again.
54-
return seen[seen.length - 1];
55+
return this[this.length - 1];
5556
}
5657
}

test/cached_async_iterable_test.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ suite("CachedAsyncIterable", function() {
5555
});
5656
});
5757

58+
suite("from()", function() {
59+
test("pass any iterable", async function() {
60+
const iterable = CachedAsyncIterable.from([1, 2]);
61+
// No cached elements yet.
62+
assert.deepEqual([...iterable], []);
63+
// Deplete the original iterable.
64+
assert.deepEqual(await toArray(iterable), [1, 2]);
65+
});
66+
67+
test("pass another CachedAsyncIterable", function() {
68+
const iterable1 = new CachedAsyncIterable([1, 2]);
69+
const iterable2 = CachedAsyncIterable.from(iterable1);
70+
assert.equal(iterable1, iterable2);
71+
});
72+
});
73+
5874
suite("sync iteration over cached elements", function(){
5975
let o1, o2;
6076

@@ -170,33 +186,33 @@ suite("CachedAsyncIterable", function() {
170186

171187
test("consumes an element into the cache", async function() {
172188
const iterable = new CachedAsyncIterable(generateMessages());
173-
assert.equal(iterable.seen.length, 0);
189+
assert.equal(iterable.length, 0);
174190
await iterable.touchNext();
175-
assert.equal(iterable.seen.length, 1);
191+
assert.equal(iterable.length, 1);
176192
});
177193

178194
test("allows to consume multiple elements into the cache", async function() {
179195
const iterable = new CachedAsyncIterable(generateMessages());
180196
await iterable.touchNext();
181197
await iterable.touchNext();
182-
assert.equal(iterable.seen.length, 2);
198+
assert.equal(iterable.length, 2);
183199
});
184200

185201
test("allows to consume multiple elements at once", async function() {
186202
const iterable = new CachedAsyncIterable(generateMessages());
187203
await iterable.touchNext(2);
188-
assert.equal(iterable.seen.length, 2);
204+
assert.equal(iterable.length, 2);
189205
});
190206

191207
test("stops at the last element", async function() {
192208
const iterable = new CachedAsyncIterable(generateMessages());
193209
await iterable.touchNext();
194210
await iterable.touchNext();
195211
await iterable.touchNext();
196-
assert.equal(iterable.seen.length, 3);
212+
assert.equal(iterable.length, 3);
197213

198214
await iterable.touchNext();
199-
assert.equal(iterable.seen.length, 3);
215+
assert.equal(iterable.length, 3);
200216
});
201217

202218
test("works on an empty iterable", async function() {
@@ -207,7 +223,7 @@ suite("CachedAsyncIterable", function() {
207223
await iterable.touchNext();
208224
await iterable.touchNext();
209225
await iterable.touchNext();
210-
assert.equal(iterable.seen.length, 1);
226+
assert.equal(iterable.length, 1);
211227
});
212228

213229
test("iteration for such cache works", async function() {

test/cached_sync_iterable_test.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ suite("CachedSyncIterable", function() {
4040
});
4141
});
4242

43+
suite("from()", function() {
44+
test("pass any iterable", function() {
45+
const iterable = CachedSyncIterable.from([1, 2]);
46+
assert.deepEqual([...iterable], [1, 2]);
47+
});
48+
49+
test("pass another CachedSyncIterable", function() {
50+
const iterable1 = new CachedSyncIterable([1, 2]);
51+
const iterable2 = CachedSyncIterable.from(iterable1);
52+
assert.equal(iterable1, iterable2);
53+
});
54+
});
55+
4356
suite("sync iteration", function(){
4457
let o1, o2;
4558

@@ -93,41 +106,41 @@ suite("CachedSyncIterable", function() {
93106

94107
test("consumes an element into the cache", function() {
95108
const iterable = new CachedSyncIterable([o1, o2]);
96-
assert.equal(iterable.seen.length, 0);
109+
assert.equal(iterable.length, 0);
97110
iterable.touchNext();
98-
assert.equal(iterable.seen.length, 1);
111+
assert.equal(iterable.length, 1);
99112
});
100113

101114
test("allows to consume multiple elements into the cache", function() {
102115
const iterable = new CachedSyncIterable([o1, o2]);
103116
iterable.touchNext();
104117
iterable.touchNext();
105-
assert.equal(iterable.seen.length, 2);
118+
assert.equal(iterable.length, 2);
106119
});
107120

108121
test("allows to consume multiple elements at once", function() {
109122
const iterable = new CachedSyncIterable([o1, o2]);
110123
iterable.touchNext(2);
111-
assert.equal(iterable.seen.length, 2);
124+
assert.equal(iterable.length, 2);
112125
});
113126

114127
test("stops at the last element", function() {
115128
const iterable = new CachedSyncIterable([o1, o2]);
116129
iterable.touchNext();
117130
iterable.touchNext();
118131
iterable.touchNext();
119-
assert.equal(iterable.seen.length, 3);
132+
assert.equal(iterable.length, 3);
120133

121134
iterable.touchNext();
122-
assert.equal(iterable.seen.length, 3);
135+
assert.equal(iterable.length, 3);
123136
});
124137

125138
test("works on an empty iterable", function() {
126139
const iterable = new CachedSyncIterable([]);
127140
iterable.touchNext();
128141
iterable.touchNext();
129142
iterable.touchNext();
130-
assert.equal(iterable.seen.length, 1);
143+
assert.equal(iterable.length, 1);
131144
});
132145

133146
test("iteration for such cache works", function() {

0 commit comments

Comments
 (0)