⚠️ Async Functions Are Not Mockable

 

⚠️ Async Functions Are Not Mockable

Async functions are convenient in JavaScript, but they are incompatible with mockable Promises like MockChain.

This post explains why async functions break mockability and how to preserve control over promise chains.


❌ Why Async Functions Break Mockability

An async function always returns a native Promise, even if you return another Promise (as a MockChain) inside it.

async function example() {
  const chain = MockChain.resolve(42);
  return chain; // You think you're returning a MockChain...
}

const result = example();
console.log(result instanceof MockChain); // ❌ false

Why this happens: ECMAScript’s Promise assimilation wraps any thenable in a new native Promise, so the original instance is lost.

Consequences:

  • Custom subclass methods (.mock(), .fail(), .root, .parent) disappear.
  • Any attached metadata (promise.parent, etc.) is not preserved.

Even returning a MockChain or attaching properties to the inner promise doesn’t survive the async wrapper.


⚙️ Example of Broken Mockability

async function doFetch(url, opts, autostart = true) {
  const chain = MockChain.fromResponse(url, opts, autostart).then(res => res.json());
  return chain; // ❌ Lost after return
}

const promise = doFetch(undefined, undefined, false);
console.log(promise instanceof MockChain); // ❌ -> false

The returned object is a native Promise with no .mock() — the MockChain identity is gone.


✅ Correct Pattern for Mockable Async Logic

Avoid async for functions that return MockChain. Use plain functions instead:

// A plain function: note that the `async` keyword is intentionally omitted
function doFetch(url, opts, autostart = true) {
  const chain = MockChain.fromResponse(url, opts, autostart).then(res => res.json());
  return chain; // ✅ still mockable
}

const chain = doFetch(undefined, undefined, false);

// Mock the root of the chain to simulate a response
chain.root.mock(new Response(JSON.stringify({ ok: true })));

// Consume the chain as usual
chain.then(data => console.log(data));
// → { ok: true }

This ensures the caller receives the original MockChain, preserving full mockability.


🧠 Key Takeaways

  • async functions always wrap returned values in native Promises.
  • Any Promise subclass (like MockChain) or attached properties are lost.
  • To preserve mockable promises, use regular functions when returning MockChain instances.

Rule of thumb: Async functions are excellent for orchestration, but not for controlled or mockable asynchronous flows.

Comments

Popular posts from this blog

🔑 Why MockChain Is a Chain

🏁 The autostart Flag