DEV Community

Ali Gürtuna | AGProLabs
Ali Gürtuna | AGProLabs

Posted on Fully Autonomous

A late response should not replace the newer view

A slow result can be valid for the request that created it and still be wrong for the view that is now open. Picture a reader switching a market basket, a directory filter or an archive search while the previous read is still pending. If the older response arrives last, it should not replace the newer view.

I maintain AGProLabs, AliGurtuna.com and Babamın Şiir Defteri. Their public resources provide concrete examples of different browsing contexts: candidate screening, health-resource directories and poet archives. The example below is a standalone read-coordination contract, not a claim that it is deployed on those sites.

Make ownership of the view explicit

Every new load gets a generation number. A result may commit only when its generation still owns the view. Success and failure need the same check: a late error from a superseded read should not erase the newer success.

The interface has four states: pending, ready, failed and idle. “Superseded” is the outcome of the old load, not a failure state displayed to the current reader. An explicit invalidation moves the view to idle and prevents a later completion from committing.

This deliberately does not cancel the underlying work. It coordinates commits. Transport cancellation, timeouts and server-side ordering need their own policies. It is intended for replaceable read results, not payments, orders or other writes that must be preserved.

A framework-independent implementation

Save as latest-reader.mjs:

export function createLatestReader(read, commit) {
  if (typeof read !== 'function' || typeof commit !== 'function')
    throw new TypeError('read and commit must be functions');
  let generation = 0;
  return {
    async load(input) {
      const ticket = ++generation;
      commit({ kind: 'pending', input });
      let value;
      try { value = await read(input); }
      catch (error) {
        if (ticket !== generation) return { kind: 'superseded' };
        commit({ kind: 'failed', input, error });
        return { kind: 'failed' };
      }
      if (ticket !== generation) return { kind: 'superseded' };
      commit({ kind: 'ready', input, value });
      return { kind: 'committed' };
    },
    invalidate() {
      ++generation;
      commit({ kind: 'idle' });
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

commit is a synchronous view-state sink. Keep it short and avoid starting another load from inside it; schedule independent actions outside that callback. If your UI schedules the state application asynchronously, keep the generation check at that later commit boundary as well. This example assumes that the callback applies the state synchronously.

Only the read operation is inside the failure-catching block. A thrown render error should not be relabeled as a source-read failure and then passed back through the same broken renderer. Application code should handle rejected load promises for unexpected sink failures.

There is one reader per replaceable view. Two widgets that should load independently need separate readers; a shared generation would make one widget's load supersede the other.

Test ordering without a live network

These tests use controllable promises. They decide which read settles first, so they do not depend on timers or a real endpoint. Save as latest-reader.test.mjs beside the module:

import test from 'node:test';
import assert from 'node:assert/strict';
import { createLatestReader } from './latest-reader.mjs';

function deferred() {
  let resolve, reject;
  const promise = new Promise((ok, fail) => { resolve = ok; reject = fail; });
  return { promise, resolve, reject };
}
function harness() {
  const jobs = new Map();
  const states = [];
  const reader = createLatestReader(key => {
    const job = deferred(); jobs.set(key, job); return job.promise;
  }, state => states.push(state));
  return { jobs, states, reader };
}
test('late success cannot replace the newer result', async () => {
  const { jobs, states, reader } = harness();
  const older = reader.load('older'); const newer = reader.load('newer');
  jobs.get('newer').resolve(2); assert.deepEqual(await newer, { kind: 'committed' });
  jobs.get('older').resolve(1); assert.deepEqual(await older, { kind: 'superseded' });
  assert.deepEqual(states.map(s => [s.kind, s.input]),
    [['pending','older'], ['pending','newer'], ['ready','newer']]);
});
test('late failure cannot overwrite a newer success', async () => {
  const { jobs, states, reader } = harness();
  const older = reader.load('older'); const newer = reader.load('newer');
  jobs.get('newer').resolve(2); await newer;
  jobs.get('older').reject(new Error('old error')); await older;
  assert.equal(states.at(-1).kind, 'ready');
  assert.equal(states.filter(s => s.kind === 'failed').length, 0);
});
test('current failure preserves its actual error', async () => {
  const { jobs, states, reader } = harness(); const error = new Error('current');
  const result = reader.load('current'); jobs.get('current').reject(error);
  assert.deepEqual(await result, { kind: 'failed' });
  assert.equal(states.at(-1).error, error);
});
test('invalidation prevents a pending completion from committing', async () => {
  const { jobs, states, reader } = harness(); const result = reader.load('current');
  reader.invalidate(); jobs.get('current').resolve(1);
  assert.deepEqual(await result, { kind: 'superseded' });
  assert.deepEqual(states.at(-1), { kind: 'idle' });
});
test('synchronous read errors use the failure state', async () => {
  const states = []; const error = new Error('read');
  const reader = createLatestReader(() => { throw error; }, s => states.push(s));
  assert.deepEqual(await reader.load('x'), { kind: 'failed' });
  assert.equal(states.at(-1).error, error);
});
test('a render error is not relabeled as a read failure', async () => {
  const kinds = []; const error = new Error('render');
  const reader = createLatestReader(async () => 1, state => {
    kinds.push(state.kind); if (state.kind === 'ready') throw error;
  });
  await assert.rejects(reader.load('x'), e => e === error);
  assert.deepEqual(kinds, ['pending', 'ready']);
});
test('invalid dependencies fail before any load', () => {
  assert.throws(() => createLatestReader(null, () => {}), TypeError);
  assert.throws(() => createLatestReader(() => {}, null), TypeError);
});
Enter fullscreen mode Exit fullscreen mode

Run node --test latest-reader.test.mjs. The Node test-runner documentation explains the built-in runner. In the local check for this article, all seven scenarios passed: late success, late failure, current failure, invalidation, synchronous read errors, renderer exceptions and invalid dependencies. These fixtures are not a browser benchmark or proof of every possible interaction.

Preserve meaning as well as ordering

A view should describe what its current result represents. For AGProLabs research, a shortlist-to-chart hand-off and a recorded journal observation are separate steps. Request ownership prevents one result from replacing another; it does not validate source freshness, analytical quality or a trading decision.

The same distinction matters for resource browsing. Health categories and psychological-awareness tools have different page roles. A successful read is not a medical diagnosis. In a literary-review archive, the returned review should match the current filter; that does not establish attribution or substitute for reading the original work.

What to add in a real application

Keep a clear loading state, a current error state and an explicit teardown/invalidation path. Add input validation, endpoint-specific caching and transport handling where appropriate. Avoid logging sensitive answers or tokens just to debug request order. A public tutorial can reproduce the race with synthetic keys and numbers.

The important boundary is small: the read may finish, but the result needs permission to commit to this view. Keeping that boundary explicit makes both the implementation and the tests easier to review.

Disclosure: I maintain the projects referenced here. This article and code were generated with AI assistance and locally tested. No production website or TradingView script was changed. The linked health and trading resources are context, not clinical or financial advice.

Top comments (0)