Skip to content

Commit fb65ee7

Browse files
authored
Merge pull request #4 from zbraniecki/store
Ensure that if the CachedAsyncIterable is called multiple times in parallel, it does return the correct value
2 parents 55dc676 + 52789dc commit fb65ee7

File tree

2 files changed

+72
-25
lines changed

2 files changed

+72
-25
lines changed

src/cached_async_iterable.mjs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,6 @@ export default class CachedAsyncIterable extends CachedIterable {
2525
}
2626
}
2727

28-
/**
29-
* Synchronous iterator over the cached elements.
30-
*
31-
* Return a generator object implementing the iterator protocol over the
32-
* cached elements of the original (async or sync) iterable.
33-
*/
34-
[Symbol.iterator]() {
35-
const cached = this;
36-
let cur = 0;
37-
38-
return {
39-
next() {
40-
if (cached.length === cur) {
41-
return {value: undefined, done: true};
42-
}
43-
return cached[cur++];
44-
}
45-
};
46-
}
47-
4828
/**
4929
* Asynchronous iterator caching the yielded elements.
5030
*
@@ -60,7 +40,7 @@ export default class CachedAsyncIterable extends CachedIterable {
6040
return {
6141
async next() {
6242
if (cached.length <= cur) {
63-
cached.push(await cached.iterator.next());
43+
cached.push(cached.iterator.next());
6444
}
6545
return cached[cur++];
6646
}
@@ -77,10 +57,10 @@ export default class CachedAsyncIterable extends CachedIterable {
7757
let idx = 0;
7858
while (idx++ < count) {
7959
const last = this[this.length - 1];
80-
if (last && last.done) {
60+
if (last && (await last).done) {
8161
break;
8262
}
83-
this.push(await this.iterator.next());
63+
this.push(this.iterator.next());
8464
}
8565
// Return the last cached {value, done} object to allow the calling
8666
// code to decide if it needs to call touchNext again.

test/cached_async_iterable_test.js

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ suite("CachedAsyncIterable", function() {
7171
});
7272
});
7373

74-
suite("sync iteration over cached elements", function(){
74+
suite.skip("sync iteration over cached elements", function(){
7575
let o1, o2;
7676

7777
suiteSetup(function() {
@@ -125,7 +125,8 @@ suite("CachedAsyncIterable", function() {
125125

126126
const iterable = new CachedAsyncIterable(generate());
127127
await iterable.touchNext();
128-
assert.deepEqual([...iterable], [o1]);
128+
let x = [...iterable];
129+
assert.deepEqual([...iterable], [o1])
129130
});
130131

131132
test("async iterable with all cached elements", async function() {
@@ -170,6 +171,47 @@ suite("CachedAsyncIterable", function() {
170171
const first = await toArray(iterable);
171172
assert.deepEqual(await toArray(iterable), first);
172173
});
174+
175+
test("lazy iterable can be called multiple times in parallel", async function() {
176+
let counter = 0;
177+
178+
async function *generate() {
179+
while (true) {
180+
counter++;
181+
yield null;
182+
}
183+
}
184+
185+
// We're testing that if the first call to asyncIterator has been
186+
// made, but the value of it has not been returned yet,
187+
// the consecutive call returns the same Promise rather than,
188+
// attempting to fetch the next item from the iterator.
189+
const iterable = new CachedAsyncIterable(generate());
190+
const [val1, val2] = await Promise.all([
191+
iterable[Symbol.asyncIterator]().next(),
192+
iterable[Symbol.asyncIterator]().next(),
193+
]);
194+
assert.equal(counter, 1);
195+
assert.equal(val1, val2);
196+
});
197+
198+
test("iterable's next can be called multiple times in parallel", async function() {
199+
let counter = 0;
200+
201+
async function *generate() {
202+
while (true) {
203+
counter++;
204+
yield null;
205+
}
206+
}
207+
208+
const iterable = new CachedAsyncIterable(generate());
209+
const iterator = iterable[Symbol.asyncIterator]();
210+
let val1 = await iterator.next();
211+
let val2 = await iterator.next();
212+
assert.equal(counter, 2);
213+
assert.notEqual(val1, val2);
214+
});
173215
});
174216

175217
suite("async touchNext", function(){
@@ -273,5 +315,30 @@ suite("CachedAsyncIterable", function() {
273315
await iterable.touchNext(),
274316
{value: undefined, done: true});
275317
});
318+
319+
test("touchNext can be called multiple times in parallel", async function() {
320+
let counter = 0;
321+
322+
async function *generate() {
323+
let value = 5;
324+
while (value-- > 0) {
325+
counter++;
326+
yield await Promise.resolve(value);
327+
}
328+
}
329+
330+
// We're testing that if the first call to asyncIterator has been
331+
// made, but the value of it has not been returned yet,
332+
// the consequitive call returns the same Promise rather than,
333+
// attempting to fetch the next item from the iterator.
334+
const iterable = new CachedAsyncIterable(generate());
335+
await Promise.all([
336+
iterable.touchNext(2),
337+
iterable[Symbol.asyncIterator]().next(),
338+
iterable.touchNext(2),
339+
iterable[Symbol.asyncIterator]().next(),
340+
]);
341+
assert.equal(counter, 4);
342+
});
276343
});
277344
});

0 commit comments

Comments
 (0)