Why is Sonarqube telling me await is redundant in async function
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
SonarQube flags await as redundant when an async function's last statement is return await somePromise(). Since async functions automatically wrap their return value in a Promise, return promise and return await promise produce the same result to the caller. The await adds unnecessary overhead — it unwraps the Promise then immediately re-wraps it. However, there is one important exception: inside a try/catch block, return await is necessary to catch rejections. SonarQube rule S4326 (or the ESLint equivalent no-return-await) targets this pattern.
The Redundant Pattern
Both versions return a Promise that resolves to the fetch result. The await in the first version unwraps the Promise, then the async function immediately wraps the result back into a Promise. This round-trip is unnecessary.
Why It Matters
Modern JavaScript engines (V8, SpiderMonkey) optimize away the extra microtask in many cases, so the performance difference is minimal. The main benefit of removing redundant await is cleaner, more intentional code.
When await IS Required: try/catch
Without await, the Promise is returned before it settles — the rejection happens outside the try/catch scope. With await, the function waits for the Promise to resolve or reject, allowing the catch block to handle errors. This is the one case where return await is necessary and correct.
SonarQube Rule Details
SonarQube and ESLint are smart enough to not flag return await inside try/catch blocks, because the await is necessary there.
ESLint Equivalent
The @typescript-eslint/return-await rule with "in-try-catch" is the most precise: it requires await inside try/catch and forbids it elsewhere.
Common Patterns and Fixes
Stack Traces Consideration
In older JavaScript engines, removing await causes the calling function to disappear from error stack traces. Modern engines with async stack traces have largely solved this, but it remains a consideration for some environments.
Common Pitfalls
- Removing
awaitinsidetry/catch: If you havetry { return await promise; } catch (e) { ... }and remove theawait, thecatchblock will never execute for Promise rejections. The rejection propagates to the caller instead of being handled locally. - Removing
awaitinsidetry/finally:finallyblocks execute before the returned value is resolved. Withoutawait, thefinallyblock runs before the Promise settles, which can cause cleanup to happen at the wrong time. - Blindly applying the rule to all files: Auto-fixing
no-return-awaitacross a codebase without checking fortry/catchcontext can introduce bugs. Use@typescript-eslint/return-awaitwith"in-try-catch"mode for safe enforcement. - Confusing with non-async functions:
return awaitin a non-asyncfunction is a syntax error (you cannot useawaitoutsideasync). SonarQube's rule only applies toasyncfunctions. - Assuming performance improvement is significant: The micro-optimization of removing one microtask tick is negligible in real applications. The primary benefit is code clarity and expressing intent — "I'm passing through this Promise" rather than "I'm awaiting and re-returning this value."
Summary
return await promiseis redundant inasyncfunctions — just usereturn promise- The one exception:
return awaitis required insidetry/catchto catch Promise rejections return awaitis also needed intry/finallyfor correct execution order- SonarQube rule S4326 and ESLint's
no-return-awaitflag this pattern - Use
@typescript-eslint/return-awaitwith"in-try-catch"for the best enforcement - The performance difference is negligible — the benefit is cleaner, more intentional code

