mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
JavaScript reads an iterator's `next` when the iterator opens and keeps calling that function, so a `next` the iterator assigns while it runs is not called. The for-of loop and the spread drain over an object-literal iterator read the field on every step; they now read it once after the open (classIteratorNextRead) and call the function they read. The checked-dynamic spread drain (%iterator.pack.checked) does the same, using the stepping the for-of and Array.from drains already use. `for await` over a synchronous iterator closes it on an early exit the way the async-from-sync iterator does: return()'s result is read for done and value, and the value is awaited, so a pending promise settles before the loop continues and a rejected one rejects the loop (after a throw from the body, the body's error still wins).
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
// for await over classes whose [Symbol.asyncIterator]() or [Symbol.iterator]()
|
|
// returns an iterator object: early exits await return(), as in Node.
|
|
class Ticks {
|
|
readonly n: number;
|
|
constructor(n: number) { this.n = n; }
|
|
[Symbol.asyncIterator]() {
|
|
let i = 0;
|
|
const n = this.n;
|
|
return {
|
|
async next(): Promise<IteratorResult<number>> {
|
|
return i < n ? { value: i++, done: false } : { value: undefined, done: true };
|
|
},
|
|
async return(): Promise<IteratorResult<number>> {
|
|
console.log("closed at", i);
|
|
return { value: undefined, done: true };
|
|
},
|
|
};
|
|
}
|
|
}
|
|
class Pages {
|
|
[Symbol.asyncIterator](): PageCursor { return new PageCursor(); }
|
|
}
|
|
class PageCursor {
|
|
page = 0;
|
|
async next(): Promise<{ value: string; done: boolean }> {
|
|
this.page++;
|
|
return this.page <= 3 ? { value: "page " + this.page, done: false } : { value: "", done: true };
|
|
}
|
|
}
|
|
class Letters {
|
|
[Symbol.iterator]() {
|
|
const letters = ["x", "y", "z"];
|
|
let i = 0;
|
|
return {
|
|
next(): IteratorResult<string> { return i < letters.length ? { value: letters[i++]!, done: false } : { value: undefined, done: true }; },
|
|
};
|
|
}
|
|
}
|
|
async function main(): Promise<void> {
|
|
let sum = 0;
|
|
for await (const v of new Ticks(4)) sum += v * 2;
|
|
console.log("sum", sum);
|
|
for await (const v of new Ticks(5)) {
|
|
if (v === 2) break;
|
|
console.log("early", v);
|
|
}
|
|
try {
|
|
for await (const v of new Ticks(5)) if (v === 1) throw new Error("body " + v);
|
|
} catch (e) {
|
|
console.log("caught", (e as Error).message);
|
|
}
|
|
async function find(): Promise<number> {
|
|
for await (const v of new Ticks(9)) if (v > 2) return v;
|
|
return -1;
|
|
}
|
|
console.log("found", await find());
|
|
for await (const p of new Pages()) console.log(p);
|
|
for await (const l of new Letters()) console.log("letter", l);
|
|
// return()'s value is awaited when the loop leaves early: a pending
|
|
// promise settles first, and a rejection rejects the loop after a break
|
|
// (after a throw from the body, the body's error wins).
|
|
for await (const v of new Closing((resolve) => setTimeout(() => resolve(1), 5)))
|
|
if (v === 1) break;
|
|
console.log("after break");
|
|
try {
|
|
for await (const v of new Closing((_, reject) => reject(new Error("closing failed"))))
|
|
if (v === 1) break;
|
|
} catch (e) {
|
|
console.log("break:", (e as Error).message);
|
|
}
|
|
try {
|
|
for await (const v of new Closing((_, reject) => reject(new Error("closing failed"))))
|
|
if (v === 1) throw new Error("body failed");
|
|
} catch (e) {
|
|
console.log("throw:", (e as Error).message);
|
|
}
|
|
}
|
|
class Closing {
|
|
readonly settle: (resolve: (v: number) => void, reject: (e: Error) => void) => void;
|
|
constructor(settle: (resolve: (v: number) => void, reject: (e: Error) => void) => void) {
|
|
this.settle = settle;
|
|
}
|
|
[Symbol.iterator]() {
|
|
let i = 0;
|
|
const settle = this.settle;
|
|
return {
|
|
next(): IteratorResult<number> {
|
|
return { value: i++, done: false };
|
|
},
|
|
return(): IteratorResult<number> {
|
|
console.log("return called");
|
|
const value = new Promise<number>((resolve, reject) =>
|
|
settle((v) => (console.log("value settled"), resolve(v)), reject),
|
|
);
|
|
return { value: value as unknown as number, done: true };
|
|
},
|
|
};
|
|
}
|
|
}
|
|
main();
|