跪拜 Guibai
← All articles
Android

How Retrofit Turns Callbacks into Kotlin Suspend Functions

By 潜龙勿用之化骨龙 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Understanding that Retrofit's suspend support is a callback-to-continuation bridge, not a blocking call, prevents developers from mistakenly wrapping every Retrofit call in withContext(Dispatchers.IO) and adding unnecessary thread switches.

Summary

When a Retrofit interface declares a suspend function, the Kotlin compiler adds a hidden Continuation parameter. Retrofit's HttpServiceMethod inspects the method signature, detects that Continuation, and routes the call to a SuspendForBody adapter. That adapter wraps OkHttp's enqueue callback inside suspendCancellableCoroutine, suspending the coroutine until the network response arrives, then resuming it with the deserialized body or an exception. The thread is never blocked; OkHttp's dispatcher handles the I/O, and the coroutine dispatcher handles resumption. Because failures are delivered via resumeWithException, callers can catch network errors with a plain try-catch block, exactly as if the exception were thrown locally.

Takeaways
Retrofit identifies a suspend function by checking whether the method's last parameter is a Continuation.
SuspendForBody is the adapter that converts Call<T> into a suspend-friendly return type.
The conversion uses suspendCancellableCoroutine to hook OkHttp's enqueue callback into coroutine suspension.
On success, continuation.resume() delivers the response body; on failure, continuation.resumeWithException() propagates the error.
Retrofit's await() is a library-defined extension function, not the same as Deferred.await() from kotlinx.coroutines.
No thread is blocked: OkHttp's dispatcher runs the network call, and the coroutine dispatcher handles resumption.
Because failures flow through resumeWithException, a standard try-catch around the suspend call catches network errors.
Conclusions

The Continuation-parameter check is a pragmatic, low-ceremony way for a Java library to detect Kotlin suspend functions without a Kotlin dependency at compile time.

Many developers add withContext(Dispatchers.IO) to Retrofit calls out of habit, but Retrofit already hands I/O to OkHttp's thread pool, so the extra context switch is pure overhead.

The naming collision between Retrofit's Call.await() and Deferred.await() causes persistent confusion, even though one bridges callbacks and the other waits on a coroutine result.

Concepts & terms
Continuation
A compiler-generated callback interface that represents the state of a suspended coroutine. Every Kotlin suspend function receives a Continuation as a hidden last parameter, which is used to resume execution with a result or an exception.
suspendCancellableCoroutine
A Kotlin standard library function that suspends the current coroutine and provides a Continuation to a block of code, allowing callback-based APIs to be bridged into coroutine suspension. It also supports cooperative cancellation.
SuspendForBody
A Retrofit internal adapter that handles suspend functions returning a body type directly. It wraps the underlying Call into a coroutine-suspendable operation using suspendCancellableCoroutine.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗