Async Without Await
ID |
swift.async_without_await |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Swift |
Tags |
async, code-smell, suspicious-code |
Description
Reports a function declared async (or async throws) whose body
never suspends — there is no await expression and no async let
binding inside. The async keyword is then dead weight: every caller
is forced into a Task / await hop for nothing, and the signature
lies about the work the function performs.
func ping() async { // FLAW — no await / async let inside
print("hi")
}
func fetch() async throws -> Data { // OK — body actually awaits
return try await load()
}
func parallel() async -> Int { // OK — async let counts
async let a = compute()
return await a
}
Rationale
async is a calling-convention modifier: it tells callers that the
function may suspend, and it forces them into an asynchronous context.
A function that never suspends pays the suspension-point overhead and
mislabels the code as cooperative work. Most importantly, it pollutes
the API: every caller now has to be async or wrap the call in a
Task, even though the function does nothing that needs it.
Awaits that live inside a nested closure or nested function are not
credited to the outer function — those suspend the inner closure, not
the function that declares async. A function that hands work off to
a Task { await … } block but otherwise does nothing asynchronous
is still flagged.
Skipped:
-
Protocol requirements (declarations without a body) — the contract may be async even if a specific implementation is not.
-
Functions whose return type mentions
AsyncSequenceorAsyncStream— their async work happens at iteration time on the consumer side, so the producer body need not containawait.