DEV Community

Vin Lookup
Vin Lookup

Posted on

Coalescing Identical In-Flight VIN Decode Requests So Duplicate Pastes Share One Upstream Call

A free VIN decode form (or thin proxy) often starts DecodeVinValues as soon as a normalized 17-character VIN is ready. Users -- and impatient front-ends -- paste the same VIN twice, double-click Decode, or remount a component that fires again while the first request is still open. Without coalescing, you pay NHTSA twice for the same pattern and race two identical payloads into the UI.

This post is about in-flight request coalescing: when a second caller asks for the same normalized VIN while a decode is already pending, return the same promise instead of opening a second upstream call. Debounce decides when to start after typing pauses. Stale-response guards decide which generation may commit. ETag caches skip repeats after a prior success. Here the focus is sharing one in-flight promise for identical VINs mid-request.

The failure mode

Typical sequence without coalesce:

  1. User pastes VIN A; you fetch and keep the promise.
  2. Before A resolves, the user pastes A again (or a React effect re-runs with the same value).
  3. You start a second DecodeVinValues for the same normalized string.
  4. NHTSA sees two identical requests; your quota and latency budget take a hit.
  5. Both responses return; even with a generation guard you still wasted upstream work.

Debounce does not fix this: both pastes already cleared the debounce window as complete 17-character candidates. Stale-response ignore only drops late commits for different VINs; identical duplicates still double-fetch. An ETag cache helps after success, not while the first call is still pending.

One promise per normalized VIN in flight

Own a map from normalized VIN to the in-flight Promise. On decode start:

  1. Normalize and validate the VIN.
  2. If the map already has an entry for that key, return it.
  3. Otherwise create the upstream promise, store it, and finally delete the key when settled (success or failure).

Callers that arrive mid-flight await the shared result. Distinct VINs still run in parallel -- coalesce is keyed by identity, not a global single-flight lock.

const VIN_RE = /^[A-HJ-NPR-Z0-9]{17}$/;

export function normalizeVin(raw: string): string {
  return raw.trim().toUpperCase().replace(/[\s\-._]/g, "");
}

export type DecodeRow = Record<string, string>;

export type CoalesceMap = Map<string, Promise<DecodeRow>>;

export function coalesceDecode(
  inflight: CoalesceMap,
  rawVin: string,
  fetchDecode: (vin: string) => Promise<DecodeRow>,
): Promise<DecodeRow> {
  const vin = normalizeVin(rawVin);
  if (!VIN_RE.test(vin)) {
    return Promise.reject(new Error("invalid_vin"));
  }

  const existing = inflight.get(vin);
  if (existing) return existing;

  const pending = fetchDecode(vin).finally(() => {
    // Only delete if we are still the mapped promise (avoid racing a newer entry).
    if (inflight.get(vin) === pending) inflight.delete(vin);
  });
  inflight.set(vin, pending);
  return pending;
}
Enter fullscreen mode Exit fullscreen mode

The finally cleanup matters. Leaving stale entries after settle would force every later lookup to await a resolved promise forever -- or worse, skip a legitimate new fetch after a failed attempt. Deleting only when the map still points at this promise keeps a retry after failure honest.

Forbidden shortcuts

Product pressure often asks for:

  1. A global single-flight lock so any second decode waits on the first -- even for a different VIN
  2. Treating coalesce as a substitute for debounce (still fire on every keystroke, "share" junk candidates)
  3. Skipping AbortSignal when the user clears the input -- shared waiters must still respect cancel UX
  4. Caching failed 5xx forever inside the coalesce map so retries never leave
  5. Coalescing by raw paste string without normalize (spaces or case create duplicate keys)

Refuse those. Coalesce identical valid in-flight work. Keep debounce for typing, generation stamps for commit races, and ETag/TTL caches for post-success repeats. Do not invent "instant decode" marketing that implies you skipped NHTSA when you only shared one call.

export function assertNoCoalesceAbuse(moduleSource: string): void {
  const banned = [
    /global.?single.?flight.?all.?vins/i,
    /skip.?nhtsa.?via.?coalesce/i,
    /instant.?decode.?no.?upstream/i,
    /coalesce.?partial.?digits/i,
  ];
  for (const re of banned) {
    if (re.test(moduleSource)) {
      throw new Error(`Coalesce module misuse: ${re}`);
    }
  }
}

export async function decodeWithCoalesce(
  inflight: CoalesceMap,
  rawVin: string,
  fetchDecode: (vin: string) => Promise<DecodeRow>,
  onShared: () => void,
): Promise<DecodeRow> {
  const vin = normalizeVin(rawVin);
  const wasShared = inflight.has(vin);
  const row = await coalesceDecode(inflight, rawVin, fetchDecode);
  if (wasShared) onShared();
  return row;
}
Enter fullscreen mode Exit fullscreen mode

Instrument onShared (or a metric) so you can see how often duplicate pastes were absorbed. That is observability, not a claim that decode is free or offline.

UI and API copy that stays honest

Prefer:

  • Silent share: second paste shows the same loading state without a second spinner flicker if you already own the session
  • Optional quiet log: "joined in-flight decode for this VIN"
  • Clear error if the shared promise rejects -- every waiter sees the same failure

Avoid:

  • "Cached instantly -- no NHTSA call" when you only coalesced an open request
  • Hiding a shared failure from secondary waiters
  • Showing success for VIN B because A's in-flight promise was incorrectly keyed

Quick checks

import assert from "node:assert/strict";

const inflight: CoalesceMap = new Map();
let calls = 0;
const fetchDecode = async (vin: string): Promise<DecodeRow> => {
  calls += 1;
  await new Promise((r) => setTimeout(r, 20));
  return { VIN: vin, Make: "TEST" };
};

const p1 = coalesceDecode(inflight, "1HGCM82633A004352", fetchDecode);
const p2 = coalesceDecode(inflight, "1hgcm82633a004352", fetchDecode);
assert.equal(p1, p2);
const [a, b] = await Promise.all([p1, p2]);
assert.equal(a.Make, "TEST");
assert.equal(b.Make, "TEST");
assert.equal(calls, 1);
assert.equal(inflight.size, 0);

const p3 = coalesceDecode(inflight, "1HGCM82633A004352", fetchDecode);
await p3;
assert.equal(calls, 2);
Enter fullscreen mode Exit fullscreen mode

Review rule: coalesce modules must share by normalized VIN only while pending, clear on settle, and never claim to replace debounce, stale-response guards, or response caches.

Takeaway

In-flight coalescing collapses duplicate pastes of the same VIN into one upstream DecodeVinValues call. Pair it with debounce (start timing), generation stamps (commit races), and ETag caches (post-success repeats) -- do not substitute one for another. Your free VIN API stays kind to NHTSA when identical mid-flight work shares a promise, then clears so the next lookup can run honestly.

I maintain VIN Lookup, a free VIN decode based on NHTSA data.

Top comments (0)