diff --git a/src/cached_async_iterable.mjs b/src/cached_async_iterable.mjs index 9c03c1e..3acc7b8 100644 --- a/src/cached_async_iterable.mjs +++ b/src/cached_async_iterable.mjs @@ -23,6 +23,34 @@ export default class CachedAsyncIterable { this.seen = []; } + /** + * Synchronous iterator over the cached elements. + * + * Return a generator object implementing the iterator protocol over the + * cached elements of the original (async or sync) iterable. + */ + [Symbol.iterator]() { + const {seen} = this; + let cur = 0; + + return { + next() { + if (seen.length === cur) { + return {value: undefined, done: true}; + } + return seen[cur++]; + } + }; + } + + /** + * Asynchronous iterator caching the yielded elements. + * + * Elements yielded by the original iterable will be cached and available + * synchronously. Returns an async generator object implementing the + * iterator protocol over the elements of the original (async or sync) + * iterable. + */ [Symbol.asyncIterator]() { const { seen, iterator } = this; let cur = 0; @@ -51,5 +79,8 @@ export default class CachedAsyncIterable { seen.push(await iterator.next()); } } + // Return the last cached {value, done} object to allow the calling + // code to decide if it needs to call touchNext again. + return seen[seen.length - 1]; } } diff --git a/src/cached_sync_iterable.mjs b/src/cached_sync_iterable.mjs index 1609809..40e742c 100644 --- a/src/cached_sync_iterable.mjs +++ b/src/cached_sync_iterable.mjs @@ -49,5 +49,8 @@ export default class CachedSyncIterable { seen.push(iterator.next()); } } + // Return the last cached {value, done} object to allow the calling + // code to decide if it needs to call touchNext again. + return seen[seen.length - 1]; } } diff --git a/test/cached_async_iterable_test.js b/test/cached_async_iterable_test.js index 407dd98..805325a 100644 --- a/test/cached_async_iterable_test.js +++ b/test/cached_async_iterable_test.js @@ -55,6 +55,75 @@ suite("CachedAsyncIterable", function() { }); }); + suite("sync iteration over cached elements", function(){ + let o1, o2; + + suiteSetup(function() { + o1 = Object(); + o2 = Object(); + }); + + test("sync iterable with no cached elements yet", function() { + function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + assert.deepEqual([...iterable], []); + }); + + test("sync iterable with a few elements cached so far", async function() { + function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + await iterable.touchNext(); + assert.deepEqual([...iterable], [o1]); + }); + + test("iterable with all cached elements", async function() { + function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + await iterable.touchNext(); + await iterable.touchNext(); + assert.deepEqual([...iterable], [o1, o2]); + }); + + test("async iterable with no cached elements yet", async function() { + async function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + assert.deepEqual([...iterable], []); + }); + + test("async iterable with a few elements cached so far", async function() { + async function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + await iterable.touchNext(); + assert.deepEqual([...iterable], [o1]); + }); + + test("async iterable with all cached elements", async function() { + async function *generate() { + yield *[o1, o2]; + } + + const iterable = new CachedAsyncIterable(generate()); + await iterable.touchNext(); + await iterable.touchNext(); + assert.deepEqual([...iterable], [o1, o2]); + }); + }); + suite("async iteration", function(){ let o1, o2; @@ -172,5 +241,21 @@ suite("CachedAsyncIterable", function() { } assert.deepEqual(values, [o1, o2]); }); + + test("returns the most recent {value, done} object", async function() { + const iterable = new CachedAsyncIterable([o1, o2]); + assert.deepEqual( + await iterable.touchNext(), + {value: o1, done: false}); + assert.deepEqual( + await iterable.touchNext(), + {value: o2, done: false}); + assert.deepEqual( + await iterable.touchNext(), + {value: undefined, done: true}); + assert.deepEqual( + await iterable.touchNext(), + {value: undefined, done: true}); + }); }); }); diff --git a/test/cached_sync_iterable_test.js b/test/cached_sync_iterable_test.js index e145a98..8ba158c 100644 --- a/test/cached_sync_iterable_test.js +++ b/test/cached_sync_iterable_test.js @@ -137,5 +137,21 @@ suite("CachedSyncIterable", function() { iterable.touchNext(); assert.deepEqual([...iterable], [o1, o2]); }); + + test("returns the most recent {value, done} object", function() { + const iterable = new CachedSyncIterable([o1, o2]); + assert.deepEqual( + iterable.touchNext(), + {value: o1, done: false}); + assert.deepEqual( + iterable.touchNext(), + {value: o2, done: false}); + assert.deepEqual( + iterable.touchNext(), + {value: undefined, done: true}); + assert.deepEqual( + iterable.touchNext(), + {value: undefined, done: true}); + }); }); });