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 AsyncSequence or AsyncStream — their async work happens at iteration time on the consumer side, so the producer body need not contain await.

Remediation

Drop the async modifier. If the function genuinely needs to schedule asynchronous work, suspend on it (await) inside the body so the modifier carries weight:

func ping() {
    print("hi")
}

func runTask() async {
    await Task { await load() }.value
}