VIEW THIS AS

Auto mode follows the Route Engine until you choose a viewpoint.

YOU ARE HERE

ROUTE CHECK

CONNECTED TO

WHAT NEXT

Use the canonical route for this room, or HELP if you are unsure.

The Gold Standard of Websites | Tracing Iterator Closing When for-of Exits Before Exhaustion

A catalogue loop stops after finding one matching record. Its iterator logs a return call. Another loop reads every record and logs no return call at all. Neither observation, by itself, means the loop has malfunctioned: reaching the end and leaving early follow different paths.

The short answer is that an ordinary synchronous for-of loop can request iterator closing when its body exits the iteration early, such as through break, a function return or an escaping throw. Natural exhaustion does not require that additional return call. Failure while obtaining the next iterator result is another distinct path. The location and kind of exit matter.

This guide uses original fictional record streams, small custom iterators and controlled generator examples. It separates the language's closing protocol from an application's resource-release promise. The examples concern ordinary synchronous for-of with const bindings, not for-in, asynchronous iteration or every consumer of an iterable.

This specialist guide belongs to the Websites root. It also connects to The Gold Standard of Education | Learning That Transfers into Life and the How X Works hub. The worked cases are original teaching examples; their numbers are stipulated so that the reasoning can be checked.

eduKateSG · Websites

Find your next learning step

Trace for-of exhaustion, early exits and failures at different stages. Check optional return calls, error priority and the limits of a closing request.

ROUTE 1 · CHAPTERS 1–3

Locate values and stopping events

Build the event ledger, then compare natural exhaustion with break.

ROUTE 2 · CHAPTERS 4–6

Follow the exit destination

Distinguish continue targets, function return and escaping body errors.

ROUTE 3 · CHAPTERS 7–8

Inspect the failure stage

Compare next-result acquisition, binding and closing-method outcomes.

ROUTE 4 · CHAPTERS 9–11

Check producer limits

Observe generator suspension, synchronous Promise returns and retained iterator state.

ROUTE 5 · CHAPTERS 12–14

Transfer and verify

Rework the archive task, build an exit matrix and report the actual lifecycle evidence.

Full chapter contents · Websites subject guide

Chapter contents

Locate values and stopping events · Chapters 1–3

1. Give the iterator a visible sequence of events

2. Follow natural exhaustion to its own stopping condition

3. Trace break as an early closing request

Follow the exit destination · Chapters 4–6

4. Separate continuing this loop from leaving an inner loop

5. Follow a function return through the active traversal

6. Keep an escaping body error separate from its cleanup effects

Inspect the failure stage · Chapters 7–8

7. Locate failures while obtaining the next value

8. Check the closing method and its returned object

Check producer limits · Chapters 9–11

9. Observe a generator finally without assuming full exhaustion

10. Keep synchronous closing separate from awaiting cleanup

11. Distinguish one iterator from a fresh iterable traversal

Transfer and verify · Chapters 12–14

12. Transfer the exit rules to a changed archive task

13. Build an exit matrix with exact observable outcomes

14. Explain the stopping event and the producer's promise

CHAPTER 1 OF 14 · Locate values and stopping events

1. Give the iterator a visible sequence of events

Back to contents

An iterable supplies an iterator through its Symbol.iterator method. An iterator supplies successive result objects through next. Those objects report whether iteration is done and, when it is not done, provide the next value. A closing method named return is optional, but its presence lets a consumer request early closing under the consumer's contract.

Start with a catalogue containing identifiers R17, R29 and R42. Give its iterator an event log. Each next call appends a next marker. The loop body appends the identifier it receives. The optional return method appends a closing marker. These separate observations let us identify who requested a value and who requested closing.

function catalogue(log) {
  const codes = ["R17", "R29", "R42"];
  let index = 0;
  return {
    [Symbol.iterator]() { return this; },
    next() {
      log.push("next");
      return index < codes.length
        ? { value: codes[index++], done: false }
        : { done: true };
    },
    return() {
      log.push("return");
      return { done: true };
    }
  };
}

This fixture returns itself as its iterator. It represents one progressing sequence, not a fresh traversal every time Symbol.iterator is called. Keep that design visible when later experiments reuse or replace it. A fresh call to catalogue creates independent progress and a new log history.

The return method deliberately records a request but does not manage an actual file, connection or browser resource. It also does not change the index in this first fixture. We can therefore study the language call without pretending the demonstration releases something it never acquired.

Before running a loop, predict next calls, body visits and return calls separately. A final list of visited identifiers cannot establish which closing method was consulted. Conversely, a return marker alone cannot establish that every record was visited. The event ledger preserves both dimensions and makes a small mechanism testable without borrowing facts about an unseen catalogue service.

Contents · Next chapter

CHAPTER 2 OF 14 · Locate values and stopping events

2. Follow natural exhaustion to its own stopping condition

Back to contents

Run a plain for-of over a fresh catalogue iterator without break or another early exit. The first next call produces R17 with done false, so the body visits R17. The next two calls produce R29 and R42 in the same way. A fourth next call produces an object whose done value is true.

That final result tells the loop there is no further body value to process. The ordinary loop finishes through its exhaustion path. It does not need to add a separate call to the iterator's return method merely because the loop is now over.

const log = [];
for (const code of catalogue(log)) log.push(code);
// next, R17, next, R29, next, R42, next

The fourth next marker is useful evidence. Three body visits do not mean only three next calls occurred. The loop needed another result to discover exhaustion. A source can know its own remaining extent internally while still communicating completion through this final protocol result.

Do not treat a value field on a done-true result as one more body item. The loop's completion decision comes first. In a controlled changed fixture, return a final object containing both done true and value END. END is not appended by the ordinary body merely because it appears in that result object.

Natural completion can still run cleanup implemented inside the producer's own exhaustion behaviour. A generator can execute its final code as it reaches its end. The point is narrower: the for-of exhaustion path does not establish an additional automatic return call. Producer work during next and a separate closing request remain different events.

For a practical report, name the final done-true result and the observed body sequence. If resource release matters, record the producer's actual release evidence separately. Neither the absence of a return marker nor a completed list of values is enough to decide that wider application question without its contract.

Contents · Previous chapter · Next chapter

CHAPTER 3 OF 14 · Locate values and stopping events

3. Trace break as an early closing request

Back to contents

Now stop after the first catalogue record. The first next call supplies R17, the body records it, and break leaves this for-of before the iterator reports natural exhaustion. The loop follows its early-exit closing path and consults the iterator's return method.

const log = [];
for (const code of catalogue(log)) {
  log.push(code);
  break;
}
// next, R17, return

There is no next call for R29 in this trace. Closing is not a request to consume all remaining values until a final done result appears. It is a different protocol operation, directed to the iterator obtained for this traversal.

The ordinary closing call uses that iterator as the receiver and supplies no loop-body value as an argument. A custom method can check its this identity in an isolated fixture. This matters when return consults state stored on the iterator rather than a free variable captured from outside it.

Calling the method named return should also be distinguished from a return statement in JavaScript source. The method is an object property whose callable value the closing operation invokes. A break statement can cause that invocation even though the loop body contains no function return statement at all.

Under a successful ordinary closing call, the method must return an object. The early-exit protocol does not need to use its value as another catalogue entry. A result such as an object with done true can express a producer's intended finish, but the method's implementation determines what state it actually changed.

Our opening custom method logs a request without advancing or disabling the iterator. Retaining that same iterator and manually calling next afterwards can still produce R29. That is a deliberate limit of the fixture, not evidence that break failed to request closing. A real producer's return method should implement the lifecycle its API promises; the language cannot infer or manufacture that resource policy from the method's name.

Contents · Previous chapter · Next chapter

CHAPTER 4 OF 14 · Follow the exit destination

4. Separate continuing this loop from leaving an inner loop

Back to contents

A continue directed at the same for-of asks that loop to move to its next iteration. It does not by itself abandon the iterator. With a fresh catalogue, continue after R17 can be followed by a next call delivering R29. No early closing request is needed merely to skip the remaining statements in one body visit.

For a clear fixture, log every code before the continue and add an after marker only for later records. Predict the three body visits and final exhaustion call. The missing after marker for R17 describes skipped body work, not an exhausted or closed source.

A labelled continue can have a different relationship to an inner for-of. Suppose an outer loop processes two batches and an inner for-of reads each batch's catalogue. A continue targeting the outer loop leaves the current inner iteration. The inner iterator can require closing because the inner loop is being abandoned before exhaustion.

const log = [];
outer: for (const batch of [1, 2]) {
  for (const code of catalogue(log)) {
    log.push(`${batch}:${code}`);
    continue outer;
  }
}
// next, 1:R17, return, next, 2:R17, return

Each inner traversal here comes from a fresh catalogue call. That keeps the example focused on exit direction rather than reuse of one partially consumed iterator. The outer source is an ordinary array and is not being used as evidence for every custom producer's cleanup behaviour.

The diagnostic question is therefore “does this completion continue the particular loop holding this iterator?” rather than “does the source contain the word continue?” Labels and nesting affect the answer. A statement can continue one loop while leaving another.

This distinction transfers to searching grouped results. Skipping one unsuitable record should preserve the current traversal. Abandoning the remaining records in a batch is another action. Write down the target loop and its active iterator before predicting cleanup calls, especially when a refactor adds nesting around previously straightforward code.

Contents · Previous chapter · Next chapter

CHAPTER 5 OF 14 · Follow the exit destination

5. Follow a function return through the active traversal

Back to contents

A function can return a matching record from inside for-of. That leaves the active iteration before natural exhaustion, so the iterator's closing path is relevant. The selected function result and the iterator's optional return-method result are different values with different jobs.

Imagine findFirst reads R17 and immediately returns a small record containing that code. The loop first receives R17 through next. The function's return statement selects its outgoing value. Before that ordinary exit completes through this iteration, the closing operation can call the iterator's return method.

function firstCode(iterator) {
  for (const code of iterator) return code;
  return "none";
}
const log = [];
firstCode(catalogue(log)); // "R17"
// log: next, return

The method's returned object does not replace R17 merely because its property name is return. It satisfies the closing protocol under the successful fixture. The function's selected result remains R17 unless another applicable completion, such as a closing failure, changes the outcome.

This is a useful place to avoid a sweeping claim that every intended return is guaranteed. Cleanup can itself throw. A function that selected R17 can still fail to return normally when the iterator's closing method fails on that path. The selected value and final observed function completion should both remain visible in a test.

Ordinary surrounding try/finally can introduce further control-flow rules. Keep those separate from this initial fixture, which contains only the loop and function return. Adding unrelated finally logic too early makes it harder to identify which operation changed the outcome.

For a catalogue feature, finding the first matching identifier is a legitimate reason to stop reading. It does not establish that every remaining record was inspected or that the producer's cleanup completed successfully. The acceptance check should record the match, the requested close and the final function outcome as separate evidence.

Contents · Previous chapter · Next chapter

CHAPTER 6 OF 14 · Follow the exit destination

6. Keep an escaping body error separate from its cleanup effects

Back to contents

Throw a distinctive error from the loop body after recording R17. This is another early exit from the active for-of. The closing operation can consult and call return while the original body error remains the reason the traversal is being abandoned.

If return logs a closing marker and returns an ordinary object, the caller catches the original body error. The log contains next, R17 and return. No later catalogue record is requested under this fixture. The body error did not skip the closing request simply because the body failed.

Now change only the return method so it also throws a different error after logging its entry. In the synchronous IteratorClose rule for an existing throw completion, that original throw retains priority. The caller still observes the original body error, although the closing method ran and had its own failure.

This rule should not be simplified into “cleanup cannot fail”. It did fail in the changed fixture. The distinction concerns which error this language path propagates to the caller. An internal marker can establish that cleanup began and threw even when the caught error is the body error.

Keep exact error identity in a controlled test, rather than compare only message text. Two Error objects can carry the same message. The evidence should say which deliberately created object escaped and which closing marker was recorded.

Earlier effects also remain earlier effects. If the body incremented a counter before throwing, the counter does not automatically return to its old value. If return recorded an attempted release before its own failure, that log entry remains. Iterator closing is not a rollback transaction for every operation executed by the producer or consumer.

For practical reporting, distinguish the triggering error, closing invocation, closing side effects and final caught completion. That structure explains a failed traversal without erasing an important cleanup failure or falsely claiming that the original catalogue work completed normally.

Contents · Previous chapter · Next chapter

CHAPTER 7 OF 14 · Inspect the failure stage

7. Locate failures while obtaining the next value

Back to contents

Failure inside next belongs to another stage. The ordinary synchronous for-of obtains an iterator result, checks that it is an object, reads its done status and, if needed, reads its value before entering the body. These protocol-reading steps are not identical to a throw from a successfully entered loop body.

Use a custom iterator whose next method logs next and immediately throws a distinctive source error. Give it a return method that would log return if called. In the ordinary fixture, the caller catches the source error and the log contains next without the additional return marker.

const log = [];
const iterator = {
  [Symbol.iterator]() { return this; },
  next() { log.push("next"); throw new Error("source failed"); },
  return() { log.push("return"); return {}; }
};
// A for-of over iterator throws before its body starts.
// The controlled log contains "next", not "return".

Changed fixtures can return a primitive instead of an iterator-result object, or an object whose done getter throws. Another can report done false but throw while reading value. These failures occur in the next-result acquisition path. Do not infer an automatic IteratorClose call from the rule already established for an escaping body throw.

This difference matters when a producer owns resources. A producer cannot safely assume that every error it throws from next will be followed by the same consumer closing call as a body break. Its lifecycle design needs to account for its own failure path under the actual consumer contract.

That is not a universal rule for every iterable consumer. Another API can arrange its steps and closing obligations differently. The existing Array.from guide develops its own mapper-failure path; this lesson keeps for-of's next-result and body stages separate.

For learner practice, move a single distinctive throw from the body into next, then into a done getter and a value getter. Keep everything else controlled and predict the log before running. The location change should explain the different close evidence; the mere presence of an exception is too coarse a classification.

A binding failure after a value has been obtained belongs to yet another identifiable stage. For a controlled extension, let next successfully produce null with done false, then use a destructuring loop binding that tries to obtain a named field from that null value. The body never begins, but the value was already delivered. The binding operation fails, and this path can request closing of the active iterator. That differs from next itself throwing before it supplies a result.

This case prevents a second overgeneralisation: saying that every failure before the first body statement skips closing. Body entry is a useful marker, but it is not the complete boundary in the language algorithm. Successfully obtaining the next value, establishing the loop binding and executing the body are separate stages with their own failure handling.

In a practical event ledger, add a delivered marker just before next returns its object. Then add a body marker as the first statement inside the loop. The null-destructuring fixture records delivery and closing without a body marker. The throwing-next fixture records the failed request without delivery or closing. Those two traces make the distinction observable without relying only on the exception message.

Keep the initial examples on simple const bindings so this extension does not obscure their results. Introduce destructuring only after the learner can identify the source-acquisition and body stages. The extra case then refines a working explanation rather than replacing it with a list of exceptions that cannot be traced.

Contents · Previous chapter · Next chapter

CHAPTER 8 OF 14 · Inspect the failure stage

8. Check the closing method and its returned object

Back to contents

An iterator can omit return. Early closing then has no such callable method to invoke, and the original normal exit can proceed under the ordinary fixture. The language's closing path does not invent a method or require every iterator object to expose one.

The lookup itself is observable when the object defines a return getter. A controlled getter can log that it was consulted before supplying the callable. If it throws, the closing operation encounters that lookup failure. Keep lookup and invocation as separate events when diagnosing a producer with active properties.

A present non-callable return value, such as a number, is not equivalent to an absent method under the normal early-exit case. The method lookup contract can produce a TypeError. Null or undefined receives the absent-method treatment. Do not classify every falsy value as absence.

If a callable return method successfully returns a primitive on a break path, closing rejects that result with a TypeError. Returning an object is the relevant protocol condition. The object does not have to provide another body record for the consumer to process.

In particular, synchronous IteratorClose does not require a done-true field from that returned object and does not repeatedly read next until done becomes true. An ordinary returned object with done false is still an object. Whether the producer remains resumable is a separate question about its implementation and retained state.

An existing body throw changes error precedence as described earlier. If a closing method returns a primitive while closing that existing throw, the original body error retains priority. A test matrix should therefore vary the initiating completion as well as the method's return shape.

The useful repair is a producer with a documented closing contract and correct results, rather than a consumer that guesses cleanup success from a vaguely shaped object. Verify method presence, callability, receiver, actual effects and final completion separately. Each observation answers a distinct part of the lifecycle question.

Contents · Previous chapter · Next chapter

CHAPTER 9 OF 14 · Check producer limits

9. Observe a generator finally without assuming full exhaustion

Back to contents

A generator provides a more surprising example of closing versus completed cleanup. A suspended generator can respond to its return method by entering a finally block. That finally block can itself yield, suspending the generator before later finally statements run.

Use an original generator that yields R17 and R29 in its main work. Its finally block logs begin cleanup, yields a pause marker, then logs end cleanup. Break from for-of after the first main value. The loop requests closing, which enters finally and reaches that yield.

const log = [];
function* records() {
  try {
    yield "R17";
    yield "R29";
  } finally {
    log.push("begin cleanup");
    yield "pause";
    log.push("end cleanup");
  }
}
const iterator = records();
for (const code of iterator) break;
// log: ["begin cleanup"]
iterator.next(); // { value: undefined, done: true }
// log now also contains "end cleanup"

The pause value is not delivered as another for-of body item after break. It is the value of the result produced by the closing return call. The loop does not keep resuming the generator merely to exhaust that finally block.

This fixture intentionally makes cleanup yield so that the boundary is visible. It is not a recommendation to design resource release around an unobserved pause. If completing a release is required before the consumer continues, the producer's contract and implementation need to establish that condition directly.

Contrast a generator finally that simply logs release and does not yield. Closing a suspended generator can then run that finally through under the corresponding ordinary fixture. The difference comes from the producer's code, not a promise that every closing request always exhausts arbitrary cleanup work.

For a practical explanation, state that return was requested, finally began, and the generator remained suspended at a yield until explicitly resumed. Those observations are more informative than either “finally did not run” or “all cleanup finished”. The controlled trace supports neither oversimplification.

Contents · Previous chapter · Next chapter

CHAPTER 10 OF 14 · Check producer limits

10. Keep synchronous closing separate from awaiting cleanup

Back to contents

The ordinary synchronous for-of closing operation calls a method and handles its immediate returned value under its synchronous protocol. It does not turn into asynchronous iteration merely because the method returns a Promise object.

A Promise is an object, so a controlled return method can supply a pending Promise while synchronous break completes its closing step. The pending operation can finish later. The immediate loop exit is not evidence that its asynchronous work has already completed.

To make this observable without timers, construct a gate Promise whose resolve function the fixture retains. Let return log close requested and return that gate. After the loop, log after loop. Only then resolve the gate and observe a separately registered reaction. This establishes the order through explicit control rather than guessed elapsed milliseconds.

The resulting sequence can show close requested and after loop before the gate's later fulfilment observation. That does not mean no asynchronous cleanup was initiated. It means this synchronous consumer did not await it as a condition of completing its break path.

For-await-of has an asynchronous iterator-closing contract and is a different language form. Choosing it, or exposing an explicit asynchronous resource operation, requires understanding the producer and consumer together. This lesson does not claim that changing one loop keyword automatically solves every resource or error-handling design.

Avoid deliberately unhandled rejected promises in a classroom cleanup experiment. They introduce host reporting behaviour that obscures the synchronous protocol question. A controlled pending gate with an attached observer is enough to demonstrate the boundary safely and clearly.

For practical work, write down what “finished” means: the iterator received a request, its method returned, a release operation settled, or the external resource was actually released. Those events may coincide in a simple synchronous producer and diverge in an asynchronous one. The acceptance evidence must correspond to the event the application needs.

Contents · Previous chapter · Next chapter

CHAPTER 11 OF 14 · Check producer limits

11. Distinguish one iterator from a fresh iterable traversal

Back to contents

Our catalogue fixture returns itself from Symbol.iterator, so the same object carries its current index across uses. Creating a fresh catalogue is different from asking that same progressing object for its iterator again. The latter returns the retained state rather than resetting it.

After breaking at R17, our deliberately minimal custom return method has only logged a marker. A subsequent next on that object can still supply R29. That result is compatible with the exact fixture, whose return method did not change the index or mark it disabled.

A generator with a non-yielding finally can behave differently after return has completed it. Resuming that completed generator does not restart its original body. Another iterable might create a fresh iterator each time, allowing a new traversal from its beginning. These are producer contracts, not interchangeable consequences of a loop ending.

For a catalogue search, state whether a helper borrows a caller's progressing iterator, owns a newly acquired traversal, or accepts a repeatable iterable. Early exit can matter to a later consumer sharing the same source state. A helper named find should not silently imply restartability that its input never promised.

The ownership question also affects cleanup responsibility. A caller and producer need agreement about resource lifetime and whether an early closing request terminates the shared traversal. The language protocol provides specific interactions; it does not decide every application's sharing policy.

In a controlled exercise, keep two separate iterator identities with identical initial values and logs. Break one early and exhaust the other. Then inspect each independently. Equal record contents do not make their progress or closing history shared.

This is useful beyond iterators because many website APIs expose objects with retained state. The habit is to label the actual instance, state the operation that advances it and distinguish creating a new instance from reusing an old one. The expected next value then follows a traceable history instead of an assumption about a familiar-looking list.

Contents · Previous chapter · Next chapter

CHAPTER 12 OF 14 · Transfer and verify

12. Transfer the exit rules to a changed archive task

Back to contents

The archive streams Q10, Q20 and Q30. A helper returns Q20 when it finds the required record. Under a fresh custom iterator with a correct closing method, predict two successful value requests, two body visits and a closing request before the helper returns normally.

Now move the failure. Let the producer throw while obtaining Q20, before that second body visit begins. The helper no longer has a successful matching value to return. Under the ordinary synchronous for-of path, the earlier rule about a body return does not imply the same additional return-method call after this source failure.

Change another condition: keep the producer successful but let the body throw on Q20. The iterator has delivered the value and the body now exits with its own throw. Closing can run, and the original body error retains the stated priority if that closing attempt also throws.

Finally, place the archive traversal inside a batch loop. A continue directed at the current archive loop asks for another archive value. A continue directed at the outer batch loop abandons the active inner traversal. Name the target before predicting which iterator receives a closing request.

These changed histories keep the same archive identifiers while varying exit location and destination. A learner who memorised only that break calls return cannot explain them all. The stronger explanation identifies the active iterator, whether a body value was obtained and which completion leaves that iteration.

For a realistic acceptance check, also decide what the archive operation needs to establish after stopping. Finding a matching identifier, recording a close request and releasing a resource are different claims. The controlled iterator verifies the first two only to the extent its fixture implements them.

The transfer task is complete when the learner can predict the event ledger and final result before running it, then explain any disagreement through a specific stage. Repeating the first catalogue log with new letters is insufficient; the changed source failure, nested target and cleanup result must change the reasoning where their contracts require it.

Contents · Previous chapter · Next chapter

CHAPTER 13 OF 14 · Transfer and verify

13. Build an exit matrix with exact observable outcomes

Back to contents

Begin with fresh iterators for exhaustion, break, same-loop continue and function return. Assert next counts, body values and closing markers independently. Reusing a partially consumed iterator across these baseline checks can hide the very difference the exercise is meant to expose.

Add an escaping body throw and compare it with a throw from next. Then test result acquisition failures separately: a primitive next result, a throwing done getter and a throwing value getter. These fixtures locate the failure stage rather than classify every exception as one closing situation.

Vary the optional return property through absence, null, a non-callable number, a throwing getter and an ordinary callable. For callable cases, inspect this identity and returned object shape. A correct implementation should not depend on the returned object's done field being true to satisfy the synchronous closing object check.

Use distinctive body and closing errors to verify precedence. Break followed by a throwing close can produce the closing error. An existing body throw followed by that same closing failure preserves the body error under the specified rule. Assert exact identity and the closing marker so the losing error's attempted operation is not erased from the evidence.

Keep the generator-yielding-finally case and controlled pending-Promise case as explicit limits. They challenge the assumption that a requested close proves every possible cleanup operation has finished. Observe the additional resume or gate resolution needed for the fixture's later effects.

The checks establish selected language behaviour in the recorded runtime and input domain. They do not demonstrate device rendering, remote resource release or a production service's lifecycle. Those require corresponding application observations. A useful test report names what its logs establish and the wider claims still needing evidence.

Contents · Previous chapter · Next chapter

CHAPTER 14 OF 14 · Transfer and verify

14. Explain the stopping event and the producer's promise

Back to contents

A useful report starts with the active iterator instance, the next-result history and the body visits. It names how the iteration ended: done-true exhaustion, break, a return from the containing function, an escaping body throw or failure while obtaining a result. Then it records closing lookup, invocation and final completion where applicable.

For the opening catalogue, the explanation is concise. Exhaustion needed a final next result reporting done and did not add a return call. Breaking after R17 instead requested early closing. The custom method logged that request, but its minimal implementation did not prove resource release or make the retained iterator unusable.

When cleanup fails, preserve both the initiating completion and closing evidence. When a generator suspends inside finally, say exactly where it paused. When synchronous closing returns a pending Promise, keep immediate method return separate from the later operation's settlement. These qualifications make the mechanism usable rather than weaken it.

For independent practice, ask a learner to create an archive exit matrix with one successful match, one source failure and one body failure. Require predicted logs and caught or returned outcomes. Then change only the close method from an ordinary object result to a throw and revise the applicable rows.

The practical repair is a producer and consumer whose lifecycle assumptions agree. The loop should stop for the intended reason, the producer should implement its promised closing actions, and tests should observe the completion condition the feature actually requires. The method name return cannot substitute for that agreement.

Once value acquisition, body control flow and closing work are separate, an iterator's final log stops looking arbitrary. Each event belongs to a specific stage. A website can then search, stop and release work deliberately while explaining the exact boundary between a language-level request and an application-level result.

Contents · Previous chapter · Return to Websites

Tracing Array.from Mapping Before Source Consumption Finishes

Tracing Array.from Mapping Before Source Consumption Finishes. Compare a different consumer: Array.from maps during iterable consumption and closes under its specified mapper-failure path.

Separating Thenable Property Reads from Later Promise Resolution Jobs

Separating Thenable Property Reads from Later Promise Resolution Jobs. Reading a then property, invoking it later and observing Promise completion are distinct stages; use this route for asynchronous boundaries.

Tracing Promise Outcomes When Finally Cleanup Fails

Tracing Promise Outcomes When Finally Cleanup Fails. A cleanup-inclusive Promise chain follows the work actually returned to it; compare that completion contract with synchronous iterator closing.

Separating WeakMap Key Lookup from Evidence of Garbage Collection

Separating WeakMap Key Lookup from Evidence of Garbage Collection. Known-key metadata lookup and collection evidence do not establish a component’s explicit cleanup or resource lifetime.

Separating Destructuring Defaults from Supplied Null Values. A binding initializer responds to an obtained undefined value; nested null and earlier source failures occupy different stages.

Add a lazy helper layer to the closing trace

Trace take quota completion and consumer return. Keep the helper’s final resume separate from its last delivered value and from natural source exhaustion.

Trace active inner and outer iterator lifecycles. Follow the inner iterator before the next outer pull, then locate closing and failure at the actual active level.

Follow a return through a delegated generator

Trace caller, wrapper and delegate requests through yield-star when a forwarded return produces a cleanup yield. Compare a later next, a second return and the wrapper’s ordinary continuation.

Frequently asked questions

Does normal for-of exhaustion automatically call return?

The ordinary exhaustion path finishes when next reports done. It does not add that separate return-method call.

Does break consume every remaining value?

No. It can request closing through the optional return method instead of repeatedly calling next.

Does continue close the iterator?

A continue directed at the same loop proceeds with that traversal. A labelled continue leaving an inner loop can close the abandoned inner iterator.

Does every thrown error lead to the same closing call?

No. An escaping body throw and a failure while obtaining the next iterator result occupy different paths.

Does a closing result have to report done true?

The synchronous closing rule checks for an object result under the normal exit case. It does not repeatedly exhaust a producer merely because that result reports done false.

Does synchronous closing await a Promise returned by return?

No. Keep ordinary for-of distinct from asynchronous iteration and verify the producer's actual completion promise.

Authoritative sources and further reading

ECMAScript: for-in and for-of body evaluation. The ordered next-result, binding, body and early-exit paths.

ECMAScript: IteratorClose. Optional method lookup, call result and completion precedence.

ECMAScript: Generator.prototype.return. Returning into a suspended generator.

MDN: for…of. Implementation documentation for synchronous iteration and early exit.

MDN: Generator.prototype.return. A yielded value inside finally can leave the generator suspended during return.

Return to the Websites subject guide, The Gold Standard of Education, or the How X Works hub.