When is it appropriate to use the “Async” suffix when naming a function? Yield, or non-blocking, or both?

Summary

This postmortem analyzes a common API‑design failure: misusing the “Async” suffix when naming functions. The confusion stems from mixing up asynchronous execution, non‑blocking behavior, and coroutines that yield. When these concepts are blurred, developers ship APIs that mislead callers, cause misuse, and create long‑term maintenance pain.

Root Cause

The core issue is treating “Async” as a catch‑all label for anything that doesn’t behave like a normal blocking function. This happens when:

  • “Async” is used to mean non‑blocking
  • “Async” is used to mean returns a promise/future/task
  • “Async” is used to mean coroutine that yields
  • “Async” is used simply because a language keyword exists (e.g., JavaScript async)

The result is an API where the suffix communicates nothing reliably.

Why This Happens in Real Systems

Real systems evolve under pressure, and naming often degrades because:

  • Engineers optimize for speed, not clarity, during early development.
  • Different languages use “async” differently, causing conceptual leakage.
  • Teams mix concurrency models (threads, event loops, coroutines, fibers).
  • Backward compatibility locks in bad names, making cleanup expensive.
  • Junior engineers assume “async = non‑blocking”, which is not universally true.

Real-World Impact

Poor naming around async behavior leads to:

  • Incorrect assumptions about execution timing
  • Deadlocks when callers assume non‑blocking behavior that isn’t guaranteed
  • Performance regressions from unexpected blocking inside “Async” functions
  • Hard‑to-debug race conditions
  • API misuse because the name doesn’t match the semantics
  • Documentation debt as teams try to explain inconsistent naming

Example or Code (if necessary and relevant)

Below is a minimal example showing the difference between async, non‑blocking, and yielding in JavaScript:

async function nonBlockingAsync() {
  return fetch("/data"); // returns immediately with a Promise
}

function yieldingCoroutine() {
  return (function* () {
    yield doWork();
    yield doMoreWork();
  })();
}

How Senior Engineers Fix It

Experienced engineers follow strict naming conventions tied to behavior, not implementation:

  • Use “Async” only when the function returns a future/promise/task
    (i.e., the caller must await or attach a continuation)
  • Do NOT use “Async” to mean non‑blocking
    Non‑blocking is an implementation detail, not a naming rule
  • Use explicit names for yielding/coroutine behavior, such as:
    • ProcessYielding
    • RenderStep
    • ComputeNextFrame
  • Document the concurrency model in the API guidelines
  • Enforce naming rules in code review
  • Avoid mixing coroutine and async/await semantics in the same naming scheme

Key principle:
“Async” should describe the type of return value, not the timing of execution.

Why Juniors Miss It

Less experienced developers often misunderstand async semantics because:

  • They equate “async” with “runs in the background”, which is not always true.
  • They assume language keywords define universal rules, even though each language differs.
  • They focus on how the function is implemented, not how it behaves to the caller.
  • They lack exposure to large-scale systems where naming mistakes cause real damage.
  • They don’t yet appreciate that API naming is a contract, not a suggestion.

Senior engineers learn—often painfully—that clear naming prevents entire classes of bugs, and “Async” must be used with precision, not enthusiasm.

Leave a Comment