Fetch a bounded batch while preserving order and partial resultsLESSON 4.04 · 4 OF 13 IN CHAPTER
PART B / APIs and background work
Step 90 of 252
LESSON 4.04 · 4 OF 13 IN CHAPTERHands-on

Fetch a bounded batch while preserving order and partial results

Application and assignment

An account dashboard shows a row for each requested account. Its API returns details at different speeds, and one account lookup can fail. The screen still needs each result beside the correct account, including an error for the failed row.

Implement the bounded asynchronous mapper described below. Inputs and output slots keep their original order, while up to K calls run concurrently. The reference is supplied for comparison after your attempt. This controls simultaneous calls in one runtime, not requests per second across a fleet.

Contract and starting evidence

“A dashboard loads details for 20 accounts. Starting every request overloads the service. Run at most three calls at once, keep input order, and display an error for one account without discarding successes. What does failure mean?”

Constructed 35-minute session. Prerequisite: runtime model. Keep the reference (download file, source below) closed until the attempt is complete.

Read the supplied code · typescript.ts
the reference · typescript.ts
/** Bounded workers: preserve input order; drain started work and report errors per item. */
export async function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<PromiseSettledResult<R>[]> {
  if (!Number.isInteger(limit) || limit < 1) throw new RangeError('positive integer limit required');
  const out: PromiseSettledResult<R>[] = new Array(items.length);
  let next = 0;
  async function worker() {
    while (next < items.length) {
      const index = next++; // No await between reading and claiming the index.
      try { out[index] = { status: 'fulfilled', value: await fn(items[index], index) }; }
      catch (reason) { out[index] = { status: 'rejected', reason }; }
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
  return out;
}

/** Aborting old work saves resources where supported. The generation guard protects correctness. */
export function latestOnly<T>(load: (query: string, signal: AbortSignal) => Promise<T>, render: (value: T) => void) {
  let generation = 0;
  let active: AbortController | undefined;
  return async (query: string): Promise<boolean> => {
    const own = ++generation;
    active?.abort();
    const controller = new AbortController();
    active = controller;
    try {
      const value = await load(query, controller.signal);
      if (own !== generation) return false;
      render(value);
      return true;
    } catch (error) {
      if (own !== generation) return false;
      throw error; // Current request failure must be visible to the caller/UI.
    }
  };
}

export class LRU<K, V> {
  private readonly values = new Map<K, V>();
  private readonly capacity: number;
  constructor(capacity: number) {
    if (!Number.isInteger(capacity) || capacity < 0) throw new RangeError('nonnegative integer capacity required');
    this.capacity = capacity;
  }
  get(key: K): V | undefined {
    if (!this.values.has(key)) return undefined;
    const value = this.values.get(key) as V;
    this.values.delete(key);
    this.values.set(key, value);
    return value;
  }
  put(key: K, value: V) {
    if (this.capacity === 0) return;
    this.values.delete(key);
    this.values.set(key, value);
    if (this.values.size > this.capacity) this.values.delete(this.values.keys().next().value as K);
  }
}

export type Bookmark = { id: string; title: string; version: number };
/** Apply a server response only if no newer local/server version is known. */
export function reconcile(current: Bookmark, incoming: Bookmark): Bookmark {
  if (current.id !== incoming.id) throw new Error('different entity');
  return incoming.version >= current.version ? incoming : current;
}

Original illustration: worker slots animation and .

Contract Expected behavior
Inputs Ordered items, async operation, positive integer K
Example [a,b,c], b fails, c finishes first → [success(a),failure(b),success(c)]
Boundaries Empty input → []; K=0 or fractional K → reject; peak active ≤ min(K,n)
Excluded QPS, fleet admission and forced cancellation
Diagram: Contract and starting evidence

Trace three calls in reverse completion order; assign each input an output slot; create K workers, each claiming an index synchronously before any await; catch each item's failure into its own slot. The counter is safe under this event-loop model, not automatically under threads. Explain every await before running the code.

Diagram: Contract and starting evidence

does this enforce 100 calls/second? Expected: no; concurrency and rate are different. Add clock-based admission for QPS. Follow-up: caller aborts but two calls ignore abort. Expected: define skipped unstarted items, retain active permits until work stops, and guard UI commits separately. A timeout cannot free a real socket.

Run from the repository root:

node --test curriculum/01-code/02-data-structures-algorithms/typescript.test.ts

Scheduling is O(n), result space O(n), active calls O(min(K,n)); elapsed time depends on service durations. Senior checks include partial failures, reverse completion and invalid K. Lead scope adds dependency budgets across replicas. Next: the threaded executor.