mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Read an iterator's next once, and await return()'s value in for await
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).
This commit is contained in:
@@ -29,9 +29,10 @@ import { lowerCollectionSpread } from "../containers/collection-methods.js";
|
||||
import { strCharsCall } from "../containers/array-construction.js";
|
||||
import { generatorDrain } from "../iterator-adapters.js";
|
||||
import { isSearchParamsIterable } from "../builtins/url.js";
|
||||
import { varRef } from "../../../ir/build.js";
|
||||
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import { arrayValueStore } from "../array-values.js";
|
||||
import { classGeneratorIterable } from "../class-iterables.js";
|
||||
import { iteratorCanStep } from "../iterator-consumption.js";
|
||||
|
||||
/** JavaScript: whether `expr` is or holds an existing checked-dynamic
|
||||
* object — a binding stored as a dyn value whose checker type is an
|
||||
@@ -717,13 +718,17 @@ function lowerTupleSpreadLiteral(
|
||||
return { kind: "seqExpr", stmts, result: { kind: "recordLit", fields, type, loc }, type, loc };
|
||||
}
|
||||
|
||||
/** Drain using checked dispatch so native class and generator iterators keep their methods.
|
||||
* `spell` is the TypeError message for a source that is not iterable
|
||||
* (notIterableSpelling). */
|
||||
/** Drain a checked-dynamic iterable: `next` is read once when the iterator
|
||||
* opens, as JS keeps it in the iterator record, so a `next` the iterator
|
||||
* assigns while it runs is not called. Runtime iterators step natively, and
|
||||
* other iterators call the `next` read (class methods keep their emitted
|
||||
* dispatch). `spell` is the TypeError message for a source that is not
|
||||
* iterable (notIterableSpelling). */
|
||||
function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, loc: SrcLoc): IrExpr {
|
||||
const name = "%iterator.pack.checked";
|
||||
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
|
||||
const ref = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: DYN, loc });
|
||||
const flag = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: BOOL, loc });
|
||||
const property = (key: string): IrExpr => ({
|
||||
kind: "dynKeyGet",
|
||||
value: ref("step"),
|
||||
@@ -731,6 +736,7 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo
|
||||
type: DYN,
|
||||
loc,
|
||||
});
|
||||
const finished: IrStmt = { kind: "return", value: ref("result"), loc };
|
||||
lowerer.liftedFns.push({
|
||||
name,
|
||||
params: [
|
||||
@@ -739,12 +745,13 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo
|
||||
],
|
||||
returnType: DYN,
|
||||
locals: [
|
||||
...["source", "iterator", "step", "result"].map((id) => ({
|
||||
...["source", "iterator", "next", "step", "value", "result"].map((id) => ({
|
||||
id,
|
||||
name: id,
|
||||
type: DYN,
|
||||
mutable: id !== "source",
|
||||
})),
|
||||
{ id: "fast", name: "fast", type: BOOL, mutable: false },
|
||||
{ id: "spell", name: "spell", type: STRING, mutable: false },
|
||||
],
|
||||
body: [
|
||||
@@ -760,6 +767,24 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo
|
||||
},
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "next",
|
||||
init: {
|
||||
kind: "dynKeyGet",
|
||||
value: ref("iterator"),
|
||||
key: { kind: "strLit", value: "next", type: STRING, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "fast",
|
||||
init: iteratorCanStep(ref("iterator"), ref("next"), loc),
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "result",
|
||||
@@ -770,25 +795,77 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo
|
||||
kind: "while",
|
||||
cond: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
body: [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "step",
|
||||
init: {
|
||||
kind: "dynInvoke",
|
||||
recv: ref("iterator"),
|
||||
method: "next",
|
||||
calleeName: "iterator.next",
|
||||
args: [],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
// Declared before the branches, which each assign it: a local
|
||||
// declared in a branch is released when the branch ends.
|
||||
{ kind: "varDecl", localId: "value", init: dynUndefinedExpr(loc), loc },
|
||||
{
|
||||
kind: "if",
|
||||
cond: { kind: "dynTest", test: "truthy", value: property("done"), type: BOOL, loc },
|
||||
then: [{ kind: "return", value: ref("result"), loc }],
|
||||
else_: null,
|
||||
cond: flag("fast"),
|
||||
then: [
|
||||
{
|
||||
kind: "assign",
|
||||
localId: "value",
|
||||
value: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.iteratorStep",
|
||||
args: [ref("iterator")],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.iteratorStepDone",
|
||||
args: [ref("iterator")],
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [finished],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
],
|
||||
else_: [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "step",
|
||||
init: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.iteratorResult",
|
||||
args: [
|
||||
{
|
||||
kind: "dynCall",
|
||||
callee: ref("next"),
|
||||
receiver: ref("iterator"),
|
||||
calleeName: "iterator.next",
|
||||
args: [],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "dynTest",
|
||||
test: "truthy",
|
||||
value: property("done"),
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [finished],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
{ kind: "assign", localId: "value", value: property("value"), loc },
|
||||
],
|
||||
loc,
|
||||
},
|
||||
{
|
||||
@@ -796,7 +873,7 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.packPush",
|
||||
args: [ref("result"), property("value")],
|
||||
args: [ref("result"), ref("value")],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
|
||||
@@ -8016,15 +8016,42 @@ export function classIteratorOf(
|
||||
};
|
||||
}
|
||||
|
||||
/** The `it.next()` step of a class iterator as an ordinary (possibly
|
||||
* virtual) method call. */
|
||||
export function classIteratorNextCall(
|
||||
/** The `next` an object-literal iterator's protocol calls, read once when
|
||||
* the iterator opens: JS keeps the `next` it reads then in the iterator
|
||||
* record, so a `next` the iterator assigns while it runs is not called.
|
||||
* Null for an iterator class, whose `next` is a method. */
|
||||
export function classIteratorNextRead(
|
||||
lowerer: Lowerer,
|
||||
cit: ClassIteratorInfo,
|
||||
itRef: IrExpr,
|
||||
loc: SrcLoc,
|
||||
): (IrExpr & { type: IrType & { kind: "func" } }) | null {
|
||||
const iterT = cit.iterT;
|
||||
if (iterT.kind === "object") return null;
|
||||
const field = lowerer.shapes.get(iterT.shapeId)!.fields.find((f) => f.name === "next")!;
|
||||
return {
|
||||
kind: "recordGet",
|
||||
obj: itRef,
|
||||
shapeId: iterT.shapeId,
|
||||
field: "next",
|
||||
type: field.type as IrType & { kind: "func" },
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The `it.next()` step of a class iterator: an ordinary (possibly
|
||||
* virtual) method call, or the call of `next`, the object-literal
|
||||
* iterator's `next` that classIteratorNextRead read when it opened. */
|
||||
export function classIteratorNextCall(
|
||||
lowerer: Lowerer,
|
||||
cit: ClassIteratorInfo,
|
||||
itRef: IrExpr,
|
||||
next: IrExpr | null,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
return classIteratorMethodCall(lowerer, cit, "next", itRef, loc);
|
||||
if (next === null || next.type.kind !== "func")
|
||||
return classIteratorMethodCall(lowerer, cit, "next", itRef, loc);
|
||||
return { kind: "callValue", callee: next, args: [], type: next.type.ret, loc };
|
||||
}
|
||||
|
||||
/** `it.next()` or `it.return()` of a class iterator: a (possibly virtual)
|
||||
@@ -8108,6 +8135,8 @@ export function classIteratorDrainCall(
|
||||
const recvT: IrType = { kind: "object", className: cit.className };
|
||||
const recvRef: IrExpr = { kind: "varRef", localId: "r.0", type: recvT, loc };
|
||||
const itRef: IrExpr = { kind: "varRef", localId: "it.0", type: cit.iterT, loc };
|
||||
const nextRead = classIteratorNextRead(lowerer, cit, itRef, loc);
|
||||
const nextRef: IrExpr | null = nextRead ? varRef("next.0", nextRead.type, loc) : null;
|
||||
const outRef: IrExpr = { kind: "varRef", localId: "out.0", type: outT, loc };
|
||||
const resRef: IrExpr = { kind: "varRef", localId: "res.0", type: cit.resultT, loc };
|
||||
const valueRead: IrExpr = {
|
||||
@@ -8122,7 +8151,7 @@ export function classIteratorDrainCall(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "res.0",
|
||||
init: classIteratorNextCall(lowerer, cit, itRef, loc),
|
||||
init: classIteratorNextCall(lowerer, cit, itRef, nextRef, loc),
|
||||
loc,
|
||||
},
|
||||
...(cit.hasDone
|
||||
@@ -8163,6 +8192,7 @@ export function classIteratorDrainCall(
|
||||
locals: [
|
||||
{ id: "r.0", name: "r", type: recvT, mutable: false },
|
||||
{ id: "it.0", name: "it", type: cit.iterT, mutable: false },
|
||||
...(nextRead ? [{ id: "next.0", name: "next", type: nextRead.type, mutable: false }] : []),
|
||||
{ id: "out.0", name: "out", type: outT, mutable: false },
|
||||
{ id: "res.0", name: "res", type: cit.resultT, mutable: true },
|
||||
],
|
||||
@@ -8179,6 +8209,9 @@ export function classIteratorDrainCall(
|
||||
init: classIteratorOpenCall(lowerer, cit, recvRef, loc),
|
||||
loc,
|
||||
},
|
||||
...(nextRead
|
||||
? [{ kind: "varDecl", localId: "next.0", init: nextRead, loc } satisfies IrStmt]
|
||||
: []),
|
||||
{
|
||||
kind: "while",
|
||||
cond: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
@@ -8226,7 +8259,7 @@ export function classIteratorRestDrainCall(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "res.0",
|
||||
init: classIteratorNextCall(lowerer, cit, itRef, loc),
|
||||
init: classIteratorNextCall(lowerer, cit, itRef, null, loc),
|
||||
loc,
|
||||
},
|
||||
...(cit.hasDone
|
||||
|
||||
@@ -2578,7 +2578,7 @@ export function lowerBindingPattern(
|
||||
const itRef = (): IrExpr => ({ kind: "varRef", localId: it.id, type: citPat.iterT, loc });
|
||||
for (const el of pattern.elements.nodes) {
|
||||
const stepLoc = locOf(el);
|
||||
const step = lowerer.classIteratorNextCall(citPat, itRef(), stepLoc);
|
||||
const step = lowerer.classIteratorNextCall(citPat, itRef(), null, stepLoc);
|
||||
if (ts.isOmittedExpression(el) || el.name === undefined) {
|
||||
out.push({ kind: "exprStmt", expr: step, loc: stepLoc });
|
||||
continue;
|
||||
@@ -10726,6 +10726,8 @@ function lowerForOfClassIterator(
|
||||
const r = lowerer.declareHiddenLocal("%cir", cit.resultT);
|
||||
const recvRef: IrExpr = { kind: "varRef", localId: recv.id, type: iterable.type, loc };
|
||||
const itRef = (): IrExpr => ({ kind: "varRef", localId: it.id, type: cit.iterT, loc });
|
||||
const nextRead = lowerer.classIteratorNextRead(cit, itRef(), loc);
|
||||
const next = nextRead ? lowerer.declareHiddenLocal("%cin", nextRead.type) : null;
|
||||
const rRef = (): IrExpr => ({ kind: "varRef", localId: r.id, type: cit.resultT, loc });
|
||||
const done = cit.hasReturn ? lowerer.declareHiddenLocal("%cid", BOOL) : null;
|
||||
if (done) done.mutable = true;
|
||||
@@ -10756,7 +10758,12 @@ function lowerForOfClassIterator(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: r.id,
|
||||
init: lowerer.classIteratorNextCall(cit, itRef(), loc),
|
||||
init: lowerer.classIteratorNextCall(
|
||||
cit,
|
||||
itRef(),
|
||||
next ? varRef(next.id, next.type, loc) : null,
|
||||
loc,
|
||||
),
|
||||
loc,
|
||||
},
|
||||
...(cit.hasDone
|
||||
@@ -10820,6 +10827,9 @@ function lowerForOfClassIterator(
|
||||
init: lowerer.classIteratorOpenCall(cit, recvRef, loc),
|
||||
loc,
|
||||
},
|
||||
...(next && nextRead
|
||||
? [{ kind: "varDecl", localId: next.id, init: nextRead, loc } satisfies IrStmt]
|
||||
: []),
|
||||
...(done
|
||||
? [
|
||||
{
|
||||
@@ -10887,6 +10897,7 @@ function lowerForOfDyn(
|
||||
done.mutable = true;
|
||||
const value = lowerer.declareHiddenLocal("%dofValue", DYN);
|
||||
const returned = lowerer.declareHiddenLocal("%dofReturn", DYN);
|
||||
const closed = lowerer.declareHiddenLocal("%dofClosed", DYN);
|
||||
const needsClose = lowerer.declareHiddenLocal("%dofNeedsClose", BOOL);
|
||||
needsClose.mutable = true;
|
||||
const error = lowerer.declareHiddenLocal("%dofError", CAUGHT);
|
||||
@@ -10952,9 +10963,10 @@ function lowerForOfDyn(
|
||||
},
|
||||
then: [
|
||||
{
|
||||
kind: "exprStmt",
|
||||
kind: "varDecl",
|
||||
localId: closed.id,
|
||||
// return()'s (awaited) result must be an object, as for next().
|
||||
expr: asyncLoop
|
||||
init: asyncLoop
|
||||
? check(
|
||||
awaitDyn({
|
||||
kind: "dynCall",
|
||||
@@ -10969,6 +10981,27 @@ function lowerForOfDyn(
|
||||
: call(varRef(returned.id, DYN, loc)),
|
||||
loc,
|
||||
},
|
||||
// A synchronous iterator's return() result is read as the
|
||||
// async-from-sync iterator reads it: done, then the value, which is
|
||||
// awaited, so a rejected promise there rejects the loop.
|
||||
...(syncIterator
|
||||
? [
|
||||
{
|
||||
kind: "if",
|
||||
cond: varRef(syncIterator.id, BOOL, loc),
|
||||
then: [
|
||||
{ kind: "exprStmt", expr: get(varRef(closed.id, DYN, loc), "done"), loc },
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: awaitDyn(get(varRef(closed.id, DYN, loc), "value")),
|
||||
loc,
|
||||
},
|
||||
],
|
||||
else_: null,
|
||||
loc,
|
||||
} satisfies IrStmt,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
else_: null,
|
||||
loc,
|
||||
|
||||
@@ -1982,7 +1982,7 @@ function classIteratorObject(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: r.id,
|
||||
init: classIteratorNextCall(lowerer, cit, varRef(it.id, cit.iterT, loc), loc),
|
||||
init: classIteratorNextCall(lowerer, cit, varRef(it.id, cit.iterT, loc), null, loc),
|
||||
loc,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -362,6 +362,7 @@ import {
|
||||
classIteratorDrainCall,
|
||||
classIteratorMethodCall,
|
||||
classIteratorNextCall,
|
||||
classIteratorNextRead,
|
||||
classIteratorOf,
|
||||
classIteratorOpenCall,
|
||||
classIteratorRestDrainCall,
|
||||
@@ -13063,8 +13064,17 @@ export class Lowerer {
|
||||
return classIteratorOpenCall(this, cit, recv, loc);
|
||||
}
|
||||
|
||||
classIteratorNextCall(cit: ClassIteratorInfo, itRef: IrExpr, loc: SrcLoc): IrExpr {
|
||||
return classIteratorNextCall(this, cit, itRef, loc);
|
||||
classIteratorNextRead(cit: ClassIteratorInfo, itRef: IrExpr, loc: SrcLoc): IrExpr | null {
|
||||
return classIteratorNextRead(this, cit, itRef, loc);
|
||||
}
|
||||
|
||||
classIteratorNextCall(
|
||||
cit: ClassIteratorInfo,
|
||||
itRef: IrExpr,
|
||||
next: IrExpr | null,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
return classIteratorNextCall(this, cit, itRef, next, loc);
|
||||
}
|
||||
|
||||
classIteratorDrainCall(src: IrExpr, loc: SrcLoc, elemT?: IrType): IrExpr | null {
|
||||
|
||||
@@ -91,3 +91,22 @@ class Range {
|
||||
let total = 0;
|
||||
for (const v of new Range(4)) total += v;
|
||||
console.log(total, [...new Range(3)].join());
|
||||
|
||||
// An object-literal iterator that replaces its next() while it runs: the
|
||||
// loop and the spread keep calling the next() they read when it opened.
|
||||
class Swapping {
|
||||
[Symbol.iterator]() {
|
||||
let i = 0;
|
||||
const it = {
|
||||
next(): { value: number; done: boolean } {
|
||||
i++;
|
||||
if (i === 2) it.next = () => ({ value: -1, done: true });
|
||||
return { value: i, done: i > 4 };
|
||||
},
|
||||
};
|
||||
return it;
|
||||
}
|
||||
}
|
||||
const swapped: number[] = [];
|
||||
for (const v of new Swapping()) swapped.push(v);
|
||||
console.log(swapped.join(), [...new Swapping()].join());
|
||||
|
||||
@@ -76,3 +76,22 @@ try {
|
||||
} catch (e) {
|
||||
console.log("throw:", e.message);
|
||||
}
|
||||
|
||||
// An object-literal iterator that replaces its next() while it runs: the
|
||||
// loop and the spread keep calling the next() they read when it opened.
|
||||
class Swapping {
|
||||
[Symbol.iterator]() {
|
||||
let i = 0;
|
||||
const it = {
|
||||
next() {
|
||||
i++;
|
||||
if (i === 2) it.next = () => ({ value: -1, done: true });
|
||||
return { value: i, done: i > 4 };
|
||||
},
|
||||
};
|
||||
return it;
|
||||
}
|
||||
}
|
||||
const swapped = [];
|
||||
for (const v of new Swapping()) swapped.push(v);
|
||||
console.log(swapped.join(), [...new Swapping()].join());
|
||||
|
||||
@@ -56,5 +56,45 @@ async function main(): Promise<void> {
|
||||
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();
|
||||
|
||||
@@ -56,5 +56,44 @@ async function main() {
|
||||
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.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.message);
|
||||
}
|
||||
}
|
||||
class Closing {
|
||||
constructor(settle) {
|
||||
this.settle = settle;
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
let i = 0;
|
||||
const settle = this.settle;
|
||||
return {
|
||||
next() {
|
||||
return { value: i++, done: false };
|
||||
},
|
||||
return() {
|
||||
console.log("return called");
|
||||
const value = new Promise((resolve, reject) =>
|
||||
settle((v) => (console.log("value settled"), resolve(v)), reject),
|
||||
);
|
||||
return { value, done: true };
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user