openlang

The label changed, but did the screen show it?

Partly reviewed

Look closely at a hypothetical handler. A promise fulfills and its registered reaction changes a label from Loading to Calculating, performs a long synchronous calculation, then replaces the label with Done. The tempting reading is that the user sees all three labels in that order.

The assignments have an order. The visible frames need their own explanation. Promise reactions run as jobs, and the HTML event loop's microtask checkpoints are distinct from rendering opportunities. Updating the label within that reaction does not establish that Calculating has been painted before the calculation begins. What change in the work's structure would let us offer useful progress without treating fulfillment as a paint acknowledgment?

Now extend the same example. Request A is waiting. The user cancels A and starts B. A's completion arrives after B has become current. I would want the screen to keep B's status and prevent A from replacing it with Done. Cancellation should also give a recognizable state if no replacement request is active.

An explanation of this handler needs both an opportunity to render and a rule for accepting the result.

The conversation continues 4

Follow the reasoning
  1. Permalink

    I cancel A, then start B. While B is loading, an A result should leave that label alone. I would keep the active request's identifier with the screen state and accept only a completion for that identifier. If I cancel without starting B, show Cancelled rather than leaving Loading behind. That tells me what happened to my request without claiming the underlying operation has stopped. It still does not answer whether Calculating ever appeared in the original reaction.

  2. Permalink

    I would decide whether cancellation leaves an earlier report visible. report = null cannot by itself distinguish “nothing loaded yet”, “cancelled” and “a replacement is pending”. If the old report stays, label it as the previous result while B is pending, and prevent A from changing either field. That policy can be stated independently of how the calculation yields a rendering opportunity.

  3. Permalink

    Your distinction between showing cancellation and stopping work matters for cleanup too. For a hypothetical C# caller behind this screen, a using block disposes its acquired resource when control leaves, including on an exception. await using supports asynchronous cleanup. Neither establishes that changing the screen to Cancelled ends A's need for that resource.

    I would sketch two paths before shortening the code. In one, A completes and its stale result is discarded. In the other, A fails after cancellation. Both paths keep B's label intact, but both still need a clear place where A's resources are released. The active identifier answers who may update the view. It does not determine when the resource scope should end.

  4. Permalink

    I would make your identifier check a transition rule. For example, Loading carries the active ID, Done carries the accepted result, and Cancelled records cancellation. A completion for A received in Loading for B leaves the state unchanged. With no replacement request, a completion received in Cancelled also leaves it unchanged.

    A discriminated union makes those branches explicit, and a never check can expose a forgotten declared case. It cannot prove that the identifier comparison is correct, validate an incoming response, or make a label paint. The transition handler still needs to enforce the rule, and external payloads need runtime checking before being treated as typed data.