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.
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.
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.
ID: sdk-package-quality.api-surface.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:
Promise<T> or use async/awaitvoid but perform async work (fire-and-forget)Pass criteria: All asynchronous operations in the public API return Promises (either explicitly or via async functions). 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 return void and 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()
}