R21BREAKINGfunction

Async to Sync

Flags when an async function becomes synchronous. Callers using .then(), .catch(), or await will crash at runtime.


Applies to

TypeScriptPythonGoJavaRust

Why it matters

The reverse of R11. Callers that await the function or chain .then() on the result will get runtime errors because the return value is no longer a Promise.

Example

Before (async)
// data.ts
export async function fetchData(url: string): Promise<Data> {
  const res = await fetch(url);
  return res.json();
}

// caller
const data = await fetchData("/api/users");
After (sync)
// data.ts (BREAKING)
export function fetchData(url: string): Data {
  return cache.get(url); // now synchronous, reads from cache
}

// caller — NOW BROKEN
const data = await fetchData("/api/users");
// 'await' on a non-Promise is a no-op in loose mode,
// but .then() would crash: fetchData(...).then is not a function

What you see in the terminal

$ npx dg check

  [BREAKING] fetchData (signature_change)
  src/data.ts:2
  Function changed from async to sync. Callers using await or .then() will encounter runtime errors.

How detection works

The classifier checks if the old signature had async: true and the new signature has async: false. It also checks if the return type changed from Promise to T.

Real-world scenario

A data fetching function is refactored to read from a cache instead of making network requests. It no longer needs to be async. All callers using `.then()` chains will crash because the return value is no longer a Promise.

Edge cases

  • await on a non-Promise is actually a no-op in JavaScript — the value is wrapped in Promise.resolve()
  • But .then() and .catch() calls will throw TypeError