Skip to content

Instantly share code, notes, and snippets.

@OliverJAsh
Last active October 22, 2021 09:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save OliverJAsh/2c327ae63941a237594eed34fe60a47b to your computer and use it in GitHub Desktop.
Save OliverJAsh/2c327ae63941a237594eed34fe60a47b to your computer and use it in GitHub Desktop.
Async iterators with map, skipWhile, and takeUntil
class FunctifiedAsync {
constructor(iterable) {
this.iterable = iterable;
}
async *[Symbol.asyncIterator]() {
for await (const value of this.iterable) {
yield value;
}
}
map(callback) {
const iterable = this.iterable;
return FunctifiedAsync.fromGenerator(async function* () {
for await (const value of iterable) {
yield callback(value);
}
});
}
skipWhile(predicate) {
const iterable = this.iterable;
return FunctifiedAsync.fromGenerator(async function* () {
let skip = true;
for await (const value of iterable) {
if (!predicate(value)) {
skip = false;
}
if (!skip) {
yield value;
}
}
});
}
flatten() {
const iterable = this.iterable;
return FunctifiedAsync.fromGenerator(async function* () {
for await (const value of iterable) {
if (value[Symbol.iterator] || value[Symbol.asyncIterator]) {
yield* new FunctifiedAsync(value);
} else {
yield value;
}
}
});
}
takeUntil(predicate) {
const iterator = this.iterable[Symbol.asyncIterator]();
const self = this;
return FunctifiedAsync.fromGenerator(async function* () {
if (self.hasOwnProperty("startValue")) {
yield self.startValue;
}
while (true) {
const result = await iterator.next();
if (result.done) {
break;
} else {
if (predicate(result.value)) {
// save the value so we can yield if takeUntil is called again
self.startValue = result.value;
break;
} else {
yield result.value;
}
}
}
});
}
static fromGenerator(generator) {
return new FunctifiedAsync({
[Symbol.asyncIterator]: generator
});
}
}
const timeoutPromise = timeout => new Promise(resolve => setTimeout(resolve, timeout))
// This could be an async API request, but here we just mock the result
const getItemsForPage = (number) => {
console.log(`getting page ${number}`)
return timeoutPromise(1000).then(() => [1, 2, 3, 4].map(x => x + (number * 4)))
}
const generateNumbers = function* (n = 0) {
yield n;
const next = n + 1;
// Recurse and delegate to the next generator
yield* generateNumbers(next)
}
async function main() {
// Paginate until we find items 7-10.
// Because we're using async iterators, this is lazy. So we won't make
// any requests after we've found our last item (takeUntil).
const asyncIterable = new FunctifiedAsync(generateNumbers())
.map(getItemsForPage)
.flatten()
.skipWhile(x => x < 7)
.takeUntil(x => x > 10)
for await (const x of asyncIterable) {
console.log(x)
}
}
main().catch(console.error)
// getting page 0
// getting page 1
// 7
// 8
// getting page 2
// 9
// 10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment