Keep Late Image Edit Responses Out of the Current Preview

calendar_today ago • schedule4 min read

A user requests a warmer background, changes the brief to remove a distraction, and starts another edit. The second request returns first. A few seconds later, the older response replaces the preview. Both requests succeeded, yet the interface now presents the wrong candidate for the visible brief.

This is an asynchronous ownership problem. Completion order says nothing about which request still owns the result slot. An image editor needs an explicit rule for admitting responses, even when its model requests are otherwise reliable. The following example is a generic JavaScript design exercise, not a description of any product's internal implementation.

Give each request a revision

Use a monotonically increasing number within one editor session. Starting a request advances it. Changing an input that changes the intended edit also advances it. Each request captures the revision at dispatch, then compares that ticket with the current revision before changing visible state.

The comparison needs to guard failures as well as successful results. Otherwise an old error can replace a newer candidate with an irrelevant failure banner. It also needs to guard loading state: an obsolete request must not hide the spinner belonging to a newer request.

Here is a small result slot. A successful admission means only that a response belongs to the latest request. The phase is deliberately called candidate, because the image still needs inspection.

export function createResultSlot() {
  let revision = 0;
  let view = Object.freeze({ phase: 'empty' });
  return Object.freeze({
    invalidate() {
      revision += 1;
      view = Object.freeze({ phase: 'empty' });
    },
    async run(request) {
      const ticket = ++revision;
      view = Object.freeze({ phase: 'pending' });
      try {
        const value = await request();
        if (ticket !== revision) return false;
        view = Object.freeze({ phase: 'candidate', value });
        return true;
      } catch {
        if (ticket !== revision) return false;
        view = Object.freeze({ phase: 'failed' });
        return false;
      }
    },
    read() {
      return view;
    }
  });
}

Connect invalidation to the actual brief

Call invalidate when the user changes the source selection, prompt, model or another setting that changes the intended task. Clear any accepted-output controls at the same point. If an earlier candidate stays visible for comparison, label it as belonging to an earlier brief rather than leaving the current download or acceptance controls attached to it.

Build the request from a snapshot captured at dispatch. For example, copy the prompt and selected option values and record stable reference identifiers. Do not let a callback read a mutable form object halfway through an asynchronous upload. A revision number cannot repair a request whose inputs were assembled from different moments.

The revision is a local ordering token, not a content hash or evidence that two image files are identical. If a user changes a prompt and then changes it back, the first request remains obsolete. That conservative rule avoids accidentally restoring a result after an unrelated model, source or setting change. Persistent history needs its own immutable task records, source identifiers and explicit restore action.

Test the response order instead of waiting for a model

Use deferred promises so the test controls which request finishes first. Start A and then B. Resolve B, confirm that its candidate is visible, then resolve A and confirm that the candidate remains B. The requests should ignore cancellation in this test; the revision check must protect correctness on its own.

Also resolve A while B is still pending. A must not clear B's loading state. Invalidate the brief while a request is pending and let the response arrive afterward; the slot must remain empty. Invalidate after a candidate is already visible and check that the candidate is cleared. Finally, reject an old request after a newer success. The newer candidate must survive the old failure.

The accompanying implementation was exercised with seven local Node test cases covering those races, current-request rejection, synchronous exceptions and shallow immutability. They use synthetic string results and deferred promises, with no model calls, uploaded images or performance benchmark. They establish the ordering behavior of this example, not the quality of generated images or integration correctness in another application.

Cancellation and rendering are separate concerns

AbortController can stop supported client-side asynchronous work such as a fetch. Use cancellation where appropriate to reduce unnecessary work, but keep the revision guard. A provider callback can ignore a cancellation request, and stopping a client request does not establish a server-side cancellation or credit refund policy. Those require a separately defined backend contract.

The same admission rule applies to subsequent image decoding or thumbnail work. If decoding happens outside request(), check the ticket again before rendering, or make the full request promise include decoding. Guard cleanup too: releasing an old object URL must not revoke the newer preview's URL. Scope each resource to the request that created it and release that resource only.

This controller catches request failures without exposing raw exceptions to the view. A production application still needs appropriate error reporting and a user-facing error message. Its frozen view is shallow: an object used as value is not automatically deeply immutable. Store a stable task or media identifier, or define separate ownership rules for mutable result objects.

Freshness is one condition for using an image

A current response can still contain altered lettering, a changed face, broken edges or a different product shape. Keep visual review separate from response admission. Likewise, source permissions, applicable model terms, current compatible settings and quoted credits require their own checks. None of these is proven by a successful promise or a matching revision.

A browser workflow such as Civon AI Image Editor provides a concrete setting for these questions: supported references and a prompt lead to a candidate that requires inspection. Its available models, usable reference counts, settings, access and credit costs vary. That context motivates the example; it does not imply that Civon uses this controller.

Which asynchronous step in your editor can still write to the preview after the user has changed the brief? Trace that path, include both success and error handling, and test it with deliberately reversed completion order.

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

AI Photo Editor Online: Upload, Describe, Edit, and Download

ethanjamescolez - Oct 19

Everyone says DeepSeek is cheaper, but I got tired of guessing the exact math. So I built a calculat

abarth23 - Apr 27

Model Switching Without Stale Controls in Text-to-Image UIs

ethanjamescolez - Nov 7

Treat each image-to-video attempt as a reviewable revision

ethanjamescolez - Nov 6

A Capability-Driven Workflow for Image-to-Video Interfaces

ethanjamescolez - Oct 28
chevron_left
288 Points • 9 Badges
7Posts
1Comments
2Connections

Related Jobs

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!