Async operations return Promises
Why it matters
Callback-based async APIs are incompatible with async/await, require consumers to wrap calls in new Promise() to compose them, and produce inverted error-handling patterns compared to modern JavaScript. ISO 25010 interoperability degrades when your API cannot be composed with Promise.all, Promise.race, or async middleware chains. Callback-only APIs also make it harder to surface errors correctly — thrown errors inside callbacks are swallowed by the event loop if the callback doesn't explicitly handle them.
Severity rationale
Medium because callback-only APIs block `async/await` usage and complicate error propagation, but do not cause immediate failures — they require consumers to add boilerplate wrapping.
Remediation
Convert callback-based methods to async functions returning typed Promises.
// Before — callback pattern:
export function fetchData(
url: string,
callback: (err: Error | null, data?: ResponseData) => void
) { /* ... */ }
// After — Promise pattern:
export async function fetchData(url: string): Promise<ResponseData> {
const response = await fetch(url)
if (!response.ok) throw new ApiError('Request failed', response.status)
return response.json() as Promise<ResponseData>
}
If you need to maintain backward compatibility during a migration, add the Promise API alongside the callback API and deprecate the callback variant with a JSDoc @deprecated tag.
Detection
-
ID:
async-promises -
Severity:
medium -
What to look for: List all async operations in the public API. For each, examine exported functions and methods that perform asynchronous operations (network requests, file I/O, database queries). Check whether they:
- Return
Promise<T>or useasync/await - Use callbacks instead of Promises
- Return
voidbut perform async work (fire-and-forget) - Have consistent async patterns (all async functions return Promises, not a mix of callbacks and Promises)
- Return
-
Pass criteria: All asynchronous operations in the public API return Promises (either explicitly or via
asyncfunctions). The package does not require consumers to pass callback functions for async results — 100% of async operations must return Promises (no callback-only APIs). Report: "X async operations found, all Y return Promises." -
Fail criteria: The public API uses callbacks for asynchronous operations (e.g.,
client.fetch(url, callback)), or async methods returnvoidand report results through event emitters or side channels without also providing a Promise-based API. -
Skip (N/A) when: The package is entirely synchronous — no async operations, no I/O, no network calls. Pure computation libraries, parsers, and formatters typically fall into this category. Also skip for Go (uses channels/goroutines) and Rust (uses different async patterns).
-
Detail on fail:
"The createClient().fetch() method takes a callback parameter instead of returning a Promise. Consumers cannot use async/await: client.fetch(url, (err, data) => { ... }). This is a Node.js callback pattern from before Promises were standard." -
Remediation: Promises are the standard async pattern in modern JavaScript. They compose with
async/await, error handling is consistent, and they work with every modern framework.// Before — callback pattern: export function fetchData( url: string, callback: (err: Error | null, data?: any) => void ) { // ... } // After — Promise pattern: export async function fetchData(url: string): Promise<ResponseData> { const response = await fetch(url) if (!response.ok) throw new ApiError(response.status) return response.json() }
External references
- iso-25010:2011 · compatibility.interoperability — Interoperability — Promise-based API composes with modern async/await toolchains
- iso-25010:2011 · maintainability.modifiability — Modifiability — callback APIs are harder to compose and migrate
Taxons
History
- 2026-04-18·v1.0.0·Initial import from sdk-package-quality·automated