Skip to content

Commit 1ba84c6

Browse files
committed
Separate out CachedSyncIterable and CachedAsyncIterable.
Copy of projectfluent/fluent.js@b44b802.
1 parent 05684dc commit 1ba84c6

6 files changed

+272
-90
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# cached-iterable
22

3-
`cached-iterable` exposes the `CachedItearble` class which implements the
4-
[iterable protocol][].
3+
`cached-iterable` exposes two classes which implement the [iterable
4+
protocol][]:
5+
6+
- `CachedSyncIterable`,
7+
- `CachedAsyncIterable`.
58

69
You can wrap any iterable in these classes to create a new iterable which
710
caches the yielded elements. This is useful for iterating over an iterable many
@@ -21,15 +24,15 @@ can install it from the npm registry or use it as a standalone script (as the
2124

2225
```js
2326
import assert from "assert";
24-
import {CachedIterable} from "cached-iterable";
27+
import {CachedSyncIterable} from "cached-iterable";
2528

2629
function * countdown(i) {
2730
while (i--) {
2831
yield i;
2932
}
3033
}
3134

32-
let numbers = new CachedIterable(countdown(3));
35+
let numbers = new CachedSyncIterable(countdown(3));
3336

3437
// `numbers` can be iterated over multiple times.
3538
assert.deepEqual([...numbers], [3, 2, 1, 0]);
@@ -42,7 +45,7 @@ For legacy browsers, the `compat` build has been transpiled using Babel's [env
4245
preset][]. It requires the regenerator runtime provided by [babel-polyfill][].
4346

4447
```javascript
45-
import {CachedIterable} from 'cached-iterable/compat';
48+
import {CachedSyncIterable} from 'cached-iterable/compat';
4649
```
4750

4851
[env preset]: https://babeljs.io/docs/plugins/preset-env/
Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/*
2-
* CachedIterable caches the elements yielded by an iterable.
2+
* CachedAsyncIterable caches the elements yielded by an async iterable.
33
*
44
* It can be used to iterate over an iterable many times without depleting the
55
* iterable.
66
*/
7-
export default class CachedIterable {
7+
export default class CachedAsyncIterable {
88
/**
9-
* Create an `CachedIterable` instance.
9+
* Create an `CachedAsyncIterable` instance.
1010
*
1111
* @param {Iterable} iterable
12-
* @returns {CachedIterable}
12+
* @returns {CachedAsyncIterable}
1313
*/
1414
constructor(iterable) {
1515
if (Symbol.asyncIterator in Object(iterable)) {
@@ -23,20 +23,6 @@ export default class CachedIterable {
2323
this.seen = [];
2424
}
2525

26-
[Symbol.iterator]() {
27-
const { seen, iterator } = this;
28-
let cur = 0;
29-
30-
return {
31-
next() {
32-
if (seen.length <= cur) {
33-
seen.push(iterator.next());
34-
}
35-
return seen[cur++];
36-
}
37-
};
38-
}
39-
4026
[Symbol.asyncIterator]() {
4127
const { seen, iterator } = this;
4228
let cur = 0;
@@ -54,11 +40,16 @@ export default class CachedIterable {
5440
/**
5541
* This method allows user to consume the next element from the iterator
5642
* into the cache.
43+
*
44+
* @param {number} count - number of elements to consume
5745
*/
58-
touchNext() {
46+
async touchNext(count = 1) {
5947
const { seen, iterator } = this;
60-
if (seen.length === 0 || seen[seen.length - 1].done === false) {
61-
seen.push(iterator.next());
48+
let idx = 0;
49+
while (idx++ < count) {
50+
if (seen.length === 0 || seen[seen.length - 1].done === false) {
51+
seen.push(await iterator.next());
52+
}
6253
}
6354
}
6455
}

src/cached_sync_iterable.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* CachedSyncIterable caches the elements yielded by an iterable.
3+
*
4+
* It can be used to iterate over an iterable many times without depleting the
5+
* iterable.
6+
*/
7+
export default class CachedSyncIterable {
8+
/**
9+
* Create an `CachedSyncIterable` instance.
10+
*
11+
* @param {Iterable} iterable
12+
* @returns {CachedSyncIterable}
13+
*/
14+
constructor(iterable) {
15+
if (Symbol.iterator in Object(iterable)) {
16+
this.iterator = iterable[Symbol.iterator]();
17+
} else {
18+
throw new TypeError("Argument must implement the iteration protocol.");
19+
}
20+
21+
this.seen = [];
22+
}
23+
24+
[Symbol.iterator]() {
25+
const { seen, iterator } = this;
26+
let cur = 0;
27+
28+
return {
29+
next() {
30+
if (seen.length <= cur) {
31+
seen.push(iterator.next());
32+
}
33+
return seen[cur++];
34+
}
35+
};
36+
}
37+
38+
/**
39+
* This method allows user to consume the next element from the iterator
40+
* into the cache.
41+
*
42+
* @param {number} count - number of elements to consume
43+
*/
44+
touchNext(count = 1) {
45+
const { seen, iterator } = this;
46+
let idx = 0;
47+
while (idx++ < count) {
48+
if (seen.length === 0 || seen[seen.length - 1].done === false) {
49+
seen.push(iterator.next());
50+
}
51+
}
52+
}
53+
}

src/index.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export {default as CachedIterable} from "./cached_iterable.mjs";
1+
export {default as CachedSyncIterable} from "./cached_sync_iterable.mjs";
2+
export {default as CachedAsyncIterable} from "./cached_async_iterable.mjs";

test/cached_async_iterable_test.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import assert from "assert";
2+
import {CachedAsyncIterable} from "../src/index";
3+
4+
/**
5+
* Return a promise for an array with all the elements of the iterable.
6+
*
7+
* It uses for-await to support async iterables which can't be spread with
8+
* ...iterable. See https://github.com/tc39/proposal-async-iteration/issues/103
9+
*
10+
*/
11+
async function toArray(iterable) {
12+
const result = [];
13+
for await (const elem of iterable) {
14+
result.push(elem);
15+
}
16+
return result;
17+
}
18+
19+
suite("CachedAsyncIterable", function() {
20+
suite("constructor errors", function(){
21+
test("no argument", function() {
22+
function run() {
23+
new CachedAsyncIterable();
24+
}
25+
26+
assert.throws(run, TypeError);
27+
assert.throws(run, /iteration protocol/);
28+
});
29+
30+
test("null argument", function() {
31+
function run() {
32+
new CachedAsyncIterable(null);
33+
}
34+
35+
assert.throws(run, TypeError);
36+
assert.throws(run, /iteration protocol/);
37+
});
38+
39+
test("bool argument", function() {
40+
function run() {
41+
new CachedAsyncIterable(1);
42+
}
43+
44+
assert.throws(run, TypeError);
45+
assert.throws(run, /iteration protocol/);
46+
});
47+
48+
test("number argument", function() {
49+
function run() {
50+
new CachedAsyncIterable(1);
51+
}
52+
53+
assert.throws(run, TypeError);
54+
assert.throws(run, /iteration protocol/);
55+
});
56+
});
57+
58+
suite("async iteration", function(){
59+
let o1, o2;
60+
61+
suiteSetup(function() {
62+
o1 = Object();
63+
o2 = Object();
64+
});
65+
66+
test("lazy iterable", async function() {
67+
async function *generate() {
68+
yield *[o1, o2];
69+
}
70+
71+
const iterable = new CachedAsyncIterable(generate());
72+
assert.deepEqual(await toArray(iterable), [o1, o2]);
73+
});
74+
75+
test("lazy iterable works more than once", async function() {
76+
async function *generate() {
77+
let i = 2;
78+
79+
while (--i) {
80+
yield Object();
81+
}
82+
}
83+
84+
const iterable = new CachedAsyncIterable(generate());
85+
const first = await toArray(iterable);
86+
assert.deepEqual(await toArray(iterable), first);
87+
});
88+
});
89+
90+
suite("async touchNext", function(){
91+
let o1, o2, generateMessages;
92+
93+
suiteSetup(function() {
94+
o1 = Object();
95+
o2 = Object();
96+
97+
generateMessages = async function *generateMessages() {
98+
yield *[o1, o2];
99+
}
100+
});
101+
102+
test("consumes an element into the cache", async function() {
103+
const iterable = new CachedAsyncIterable(generateMessages());
104+
assert.equal(iterable.seen.length, 0);
105+
await iterable.touchNext();
106+
assert.equal(iterable.seen.length, 1);
107+
});
108+
109+
test("allows to consume multiple elements into the cache", async function() {
110+
const iterable = new CachedAsyncIterable(generateMessages());
111+
await iterable.touchNext();
112+
await iterable.touchNext();
113+
assert.equal(iterable.seen.length, 2);
114+
});
115+
116+
test("allows to consume multiple elements at once", async function() {
117+
const iterable = new CachedAsyncIterable(generateMessages());
118+
await iterable.touchNext(2);
119+
assert.equal(iterable.seen.length, 2);
120+
});
121+
122+
test("stops at the last element", async function() {
123+
const iterable = new CachedAsyncIterable(generateMessages());
124+
await iterable.touchNext();
125+
await iterable.touchNext();
126+
await iterable.touchNext();
127+
assert.equal(iterable.seen.length, 3);
128+
129+
await iterable.touchNext();
130+
assert.equal(iterable.seen.length, 3);
131+
});
132+
133+
test("works on an empty iterable", async function() {
134+
async function *generateEmptyMessages() {
135+
yield *[];
136+
}
137+
const iterable = new CachedAsyncIterable(generateEmptyMessages());
138+
await iterable.touchNext();
139+
await iterable.touchNext();
140+
await iterable.touchNext();
141+
assert.equal(iterable.seen.length, 1);
142+
});
143+
144+
test("iteration for such cache works", async function() {
145+
const iterable = new CachedAsyncIterable(generateMessages());
146+
await iterable.touchNext();
147+
await iterable.touchNext();
148+
await iterable.touchNext();
149+
150+
// It's a bit quirky compared to the sync counterpart,
151+
// but there's no good way to fold async iterator into
152+
// an array.
153+
let values = [];
154+
for await (let elem of iterable) {
155+
values.push(elem);
156+
}
157+
assert.deepEqual(values, [o1, o2]);
158+
});
159+
160+
test("async version handles sync iterator", async function() {
161+
const iterable = new CachedAsyncIterable([o1, o2]);
162+
await iterable.touchNext();
163+
await iterable.touchNext();
164+
await iterable.touchNext();
165+
166+
// It's a bit quirky compared to the sync counterpart,
167+
// but there's no good way to fold async iterator into
168+
// an array.
169+
let values = [];
170+
for await (let elem of iterable) {
171+
values.push(elem);
172+
}
173+
assert.deepEqual(values, [o1, o2]);
174+
});
175+
});
176+
});

0 commit comments

Comments
 (0)