-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate out CachedSyncIterable and CachedAsyncIterable.
Copy of projectfluent/fluent.js@b44b802.
- Loading branch information
Showing
6 changed files
with
272 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* CachedSyncIterable caches the elements yielded by an iterable. | ||
* | ||
* It can be used to iterate over an iterable many times without depleting the | ||
* iterable. | ||
*/ | ||
export default class CachedSyncIterable { | ||
/** | ||
* Create an `CachedSyncIterable` instance. | ||
* | ||
* @param {Iterable} iterable | ||
* @returns {CachedSyncIterable} | ||
*/ | ||
constructor(iterable) { | ||
if (Symbol.iterator in Object(iterable)) { | ||
this.iterator = iterable[Symbol.iterator](); | ||
} else { | ||
throw new TypeError("Argument must implement the iteration protocol."); | ||
} | ||
|
||
this.seen = []; | ||
} | ||
|
||
[Symbol.iterator]() { | ||
const { seen, iterator } = this; | ||
let cur = 0; | ||
|
||
return { | ||
next() { | ||
if (seen.length <= cur) { | ||
seen.push(iterator.next()); | ||
} | ||
return seen[cur++]; | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* This method allows user to consume the next element from the iterator | ||
* into the cache. | ||
* | ||
* @param {number} count - number of elements to consume | ||
*/ | ||
touchNext(count = 1) { | ||
const { seen, iterator } = this; | ||
let idx = 0; | ||
while (idx++ < count) { | ||
if (seen.length === 0 || seen[seen.length - 1].done === false) { | ||
seen.push(iterator.next()); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export {default as CachedIterable} from "./cached_iterable.mjs"; | ||
export {default as CachedSyncIterable} from "./cached_sync_iterable.mjs"; | ||
export {default as CachedAsyncIterable} from "./cached_async_iterable.mjs"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import assert from "assert"; | ||
import {CachedAsyncIterable} from "../src/index"; | ||
|
||
/** | ||
* Return a promise for an array with all the elements of the iterable. | ||
* | ||
* It uses for-await to support async iterables which can't be spread with | ||
* ...iterable. See https://github.com/tc39/proposal-async-iteration/issues/103 | ||
* | ||
*/ | ||
async function toArray(iterable) { | ||
const result = []; | ||
for await (const elem of iterable) { | ||
result.push(elem); | ||
} | ||
return result; | ||
} | ||
|
||
suite("CachedAsyncIterable", function() { | ||
suite("constructor errors", function(){ | ||
test("no argument", function() { | ||
function run() { | ||
new CachedAsyncIterable(); | ||
} | ||
|
||
assert.throws(run, TypeError); | ||
assert.throws(run, /iteration protocol/); | ||
}); | ||
|
||
test("null argument", function() { | ||
function run() { | ||
new CachedAsyncIterable(null); | ||
} | ||
|
||
assert.throws(run, TypeError); | ||
assert.throws(run, /iteration protocol/); | ||
}); | ||
|
||
test("bool argument", function() { | ||
function run() { | ||
new CachedAsyncIterable(1); | ||
} | ||
|
||
assert.throws(run, TypeError); | ||
assert.throws(run, /iteration protocol/); | ||
}); | ||
|
||
test("number argument", function() { | ||
function run() { | ||
new CachedAsyncIterable(1); | ||
} | ||
|
||
assert.throws(run, TypeError); | ||
assert.throws(run, /iteration protocol/); | ||
}); | ||
}); | ||
|
||
suite("async iteration", function(){ | ||
let o1, o2; | ||
|
||
suiteSetup(function() { | ||
o1 = Object(); | ||
o2 = Object(); | ||
}); | ||
|
||
test("lazy iterable", async function() { | ||
async function *generate() { | ||
yield *[o1, o2]; | ||
} | ||
|
||
const iterable = new CachedAsyncIterable(generate()); | ||
assert.deepEqual(await toArray(iterable), [o1, o2]); | ||
}); | ||
|
||
test("lazy iterable works more than once", async function() { | ||
async function *generate() { | ||
let i = 2; | ||
|
||
while (--i) { | ||
yield Object(); | ||
} | ||
} | ||
|
||
const iterable = new CachedAsyncIterable(generate()); | ||
const first = await toArray(iterable); | ||
assert.deepEqual(await toArray(iterable), first); | ||
}); | ||
}); | ||
|
||
suite("async touchNext", function(){ | ||
let o1, o2, generateMessages; | ||
|
||
suiteSetup(function() { | ||
o1 = Object(); | ||
o2 = Object(); | ||
|
||
generateMessages = async function *generateMessages() { | ||
yield *[o1, o2]; | ||
} | ||
}); | ||
|
||
test("consumes an element into the cache", async function() { | ||
const iterable = new CachedAsyncIterable(generateMessages()); | ||
assert.equal(iterable.seen.length, 0); | ||
await iterable.touchNext(); | ||
assert.equal(iterable.seen.length, 1); | ||
}); | ||
|
||
test("allows to consume multiple elements into the cache", async function() { | ||
const iterable = new CachedAsyncIterable(generateMessages()); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
assert.equal(iterable.seen.length, 2); | ||
}); | ||
|
||
test("allows to consume multiple elements at once", async function() { | ||
const iterable = new CachedAsyncIterable(generateMessages()); | ||
await iterable.touchNext(2); | ||
assert.equal(iterable.seen.length, 2); | ||
}); | ||
|
||
test("stops at the last element", async function() { | ||
const iterable = new CachedAsyncIterable(generateMessages()); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
assert.equal(iterable.seen.length, 3); | ||
|
||
await iterable.touchNext(); | ||
assert.equal(iterable.seen.length, 3); | ||
}); | ||
|
||
test("works on an empty iterable", async function() { | ||
async function *generateEmptyMessages() { | ||
yield *[]; | ||
} | ||
const iterable = new CachedAsyncIterable(generateEmptyMessages()); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
assert.equal(iterable.seen.length, 1); | ||
}); | ||
|
||
test("iteration for such cache works", async function() { | ||
const iterable = new CachedAsyncIterable(generateMessages()); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
|
||
// It's a bit quirky compared to the sync counterpart, | ||
// but there's no good way to fold async iterator into | ||
// an array. | ||
let values = []; | ||
for await (let elem of iterable) { | ||
values.push(elem); | ||
} | ||
assert.deepEqual(values, [o1, o2]); | ||
}); | ||
|
||
test("async version handles sync iterator", async function() { | ||
const iterable = new CachedAsyncIterable([o1, o2]); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
await iterable.touchNext(); | ||
|
||
// It's a bit quirky compared to the sync counterpart, | ||
// but there's no good way to fold async iterator into | ||
// an array. | ||
let values = []; | ||
for await (let elem of iterable) { | ||
values.push(elem); | ||
} | ||
assert.deepEqual(values, [o1, o2]); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.