Mixing .then() chains and async/await in the same codebase — or in the same function — creates inconsistent error propagation: .then() chains require .catch() while async/await uses try/catch, and mixing them means some rejections are caught by one mechanism and some by the other. ISO 25010:2011 §6.5.2 classifies this as a maintainability defect. Nested .then() chains also re-introduce the callback-pyramid problem that async/await was designed to solve, making control flow harder to read and independent async operations sequential when they could run in parallel.
Low because mixed async styles do not cause immediate failures but produce inconsistent error handling that leads to unhandled rejections and makes the control flow significantly harder to read and maintain.
Standardize on async/await throughout. Use Promise.all() for genuinely independent async operations — never await them sequentially when they don't depend on each other's results.
// Bad: nested .then() chains, mixed with async
async function loadUserData(id: string) {
return fetchUser(id)
.then((user) => fetchUserPosts(user.id)
.then((posts) => ({ user, posts })))
.catch((err) => console.error(err))
}
// Good: consistent async/await
async function loadUserData(id: string) {
try {
const user = await fetchUser(id)
const posts = await fetchUserPosts(user.id)
return { user, posts }
} catch (err) {
logger.error('Failed to load user data', err)
throw err
}
}
// Good: parallel operations with Promise.all
async function loadDashboard(userId: string) {
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchUserPosts(userId),
])
return { user, posts }
}
ID: code-quality-essentials.organization.async-consistency
Severity: low
What to look for: Scan source files for async patterns. Look for mixing of .then()/.catch() promise chains with async/await in the same file or function. Also look for: nested .then() chains (the "callback pyramid" anti-pattern in promise form), new Promise(resolve => resolve(value)) wrapping a value that could just be returned directly, async functions that don't use await (unnecessary async overhead), uncaught rejections from .then() chains without .catch() handlers, and mixing Promise.all() correctly vs sequentially awaiting independent operations. async/await is the preferred pattern for readability — .then() is acceptable for simple transformations or when chaining is genuinely cleaner, but mixing both styles in the same function is a code smell.
Pass criteria: Enumerate all relevant code locations. Consistent use of async/await throughout. .then() chains used only where they genuinely improve readability (rare). Promise.all() used for independent parallel async operations with at least 1 verified location.
Fail criteria: Same module or function mixes .then() chains and async/await without clear reason. Nested promise chains where await would be cleaner.
Skip (N/A) when: Project has no asynchronous code.
Detail on fail: "Mixed async styles: .then() chains and async/await used in the same files; inconsistent error handling patterns"
Remediation: Refactor .then() chains to async/await:
// Bad: mixed styles, harder to read
async function loadUserData(id: string) {
return fetchUser(id)
.then((user) => {
return fetchUserPosts(user.id)
.then((posts) => ({ user, posts }))
})
.catch((err) => console.error(err))
}
// Good: consistent async/await
async function loadUserData(id: string) {
try {
const user = await fetchUser(id)
const posts = await fetchUserPosts(user.id)
return { user, posts }
} catch (err) {
logger.error('Failed to load user data', err)
throw err
}
}
// Good: parallel operations with Promise.all
async function loadDashboard(userId: string) {
const [user, posts, notifications] = await Promise.all([
fetchUser(userId),
fetchUserPosts(userId),
fetchNotifications(userId),
])
return { user, posts, notifications }
}