Skip to content

Instantly share code, notes, and snippets.

@jorendorff
Last active September 30, 2023 03:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jorendorff/35504c2553170be98fc2810ccf60c608 to your computer and use it in GitHub Desktop.
Save jorendorff/35504c2553170be98fc2810ccf60c608 to your computer and use it in GitHub Desktop.
"use strict";
Iterator.prototype = {
*map(mapper) {
for (let value of this) {
yield mapper(value);
}
},
...
};
"use strict";
function IteratorRecord(iter) {
if (Object(iter) !== iter) {
throw new TypeError("iterator object required");
}
return {iterator: iter, nextMethod: iter.next, done: false};
}
class MapIterator {
#status = "suspended"; // "suspended" | "running" | "completed"
#inner;
#fn;
constructor(inner, fn) {
this.#inner = IteratorRecord(inner);
if (typeof fn !== "function") {
throw new TypeError("map requires a function");
}
this.#fn = fn;
}
#forward(methodName, args) {
if (this.#status === "completed") {
return {value: undefined, done: true};
}
if (this.#status === "running") {
throw new TypeError("already running");
}
// Status is "suspended", the usual case.
this.#status = "running";
let done, value;
try {
let iter = this.#inner.iterator;
let method = (methodName === "next" ? this.#inner.nextMethod : iter[methodName]);
let result = Call(iter, method, args);
done = !!result.done;
value = result.value;
} catch (exc) {
this.#markComplete();
throw exc;
}
if (done) {
this.#markComplete();
} else {
// Pass this value to the mapping-function.
try {
let fn = this.#fn;
value = fn(value);
} catch (exc) {
this.#iteratorClose();
this.#markComplete();
throw exc;
}
this.#status = "suspended";
}
return {value, done};
}
#markComplete() {
this.#status = "completed";
this.#inner = undefined;
this.#fn = undefined;
}
#iteratorClose() {
try {
this.#inner.iterator.return();
} catch {
// Ignore this error.
}
}
next(value) {
return this.#forward("next", arguments.length === 0 ? [] : [value]);
}
throw(value) {
return this.#forward("throw", [value]);
}
return(value) {
return this.#forward("return", [value]);
}
}
// Do not expose the constructor.
delete MapIterator.prototype.constructor;
Iterator.prototype = {
map(mapper) {
return new MapIterator(this, mapper);
},
...
};
"use strict";
function IteratorRecord(iter) {
if (Object(iter) !== iter) {
throw new TypeError("iterator object required");
}
return {iterator: iter, nextMethod: iter.next, done: false};
}
function IteratorClose(iter) {
try {
iter.return();
} catch {
// Ignore the error.
}
}
function* SyncMap(inner, mapper) {
if (typeof mapper !== "function") {
throw new TypeError("not a function");
}
let iteratorRecord = IteratorRecord(inner);
let outVal = undefined;
while (true) {
let sent;
let yieldCompletionType = "return";
let result;
try {
sent = yield outVal;
yieldCompletionType = "normal";
} catch (exc) {
yieldCompletionType = "throw";
result = inner.throw(exc);
} finally {
if (yieldCompletionType === "return") {
// Caller did `.return(value)`. We are returning.
// We don't have access to the value being returned.
// A polyfill can get access to that value by patching
// %GeneratorPrototype%.return.
inner.return(undefined);
// After the finally block, generator will exit.
}
}
if (yieldCompletionType === "normal") {
result = Call(iteratorRecord.iterator, iteratorRecord.nextMethod, [sent]);
}
let {done, value} = result;
if (done) {
return value;
}
try {
outVal = mapper(value);
} catch (exc) {
IteratorClose(inner, {type: "throw", value: exc}); // always throws
}
}
}
Iterator.prototype = {
map(mapper) {
let it = SyncMap(this, mapper);
it.next();
return it;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment