NOTE: All examples in this README use runBlocking to build coroutine but it is only useful for testing or examples.
For a real application you probably want to use some other coroutines builder that does not block a thread, for example launch from kotlinx.coroutines.
Common await API that returns a result or throws an exception
fun Call<T>.await(): T
In case of an HTTP error or an invocation exception await() throws an exception
// You can use retrofit suspended extension inside any coroutine blockfunmain(args:Array<String>): Unit= runBlocking {
try {
// Wait (suspend) for resultval user:User= api.getUser("username").await()
// Now we can work with result objectprintln("User ${user.name} loaded")
} catch (e:HttpException) {
// Catch http errorsprintln("exception${e.code()}", e)
} catch (e:Throwable) {
// All other exceptions (non-http)println("Something broken", e)
}
}
.awaitResponse()
Common await API that returns a Response or throws an exception
fun Call<T>.awaitResponse(): Response<T>
In case of an invocation exception awaitResponse() throws an exception
// You can use retrofit suspended extension inside any coroutine blockfunmain(args:Array<String>): Unit= runBlocking {
try {
// Wait (suspend) for responseval response:Response<User> = api.getUser("username").awaitResponse()
if (response.isSuccessful()) {
// Now we can work with response objectprintln("User ${response.body().name} loaded")
}
} catch (e:Throwable) {
// All other exceptions (non-http)println("Something broken", e)
}
}
funmain(args:Array<String>): Unit= runBlocking {
// Wait (suspend) for Resultval result:Result<User> = api.getUser("username").awaitResult()
// Check result typewhen (result) {
//Successful HTTP resultisResult.Ok-> saveToDb(result.value)
// Any HTTP errorisResult.Error-> log("HTTP error with code ${result.error.code()}", result.error)
// Exception while request invocationisResult.Exception-> log("Something broken", e)
}
}
Also, Result has a few handy extension functions that allow to avoid when block matching:
funmain(args:Array<String>): Unit= runBlocking {
val result:User= api.getUser("username").awaitResult()
//Return value for success or null for any http error or exception
result.getOrNull()
//Return result or default value
result.getOrDefault(User("empty-user"))
//Return value or throw exception (HttpException or original exception)
result.getOrThrow()
//Also supports custom exceptions to override original ones
result.getOrThrow(IlleagalStateException("User request failed"))
}
All Result classes also implemented one or both interfaces: ResponseResult and ErrorResult
You can use them for access to shared properties of different classes from Result
funmain(args:Array<String>): Unit= runBlocking {
val result:User= api.getUser("username").awaitResult()
//Result.Ok and Result.Error both implement ResponseResultif (result isResponseResult) {
//And after smart cast you now have an access to okhttp3 Response property of resultprintln("Result ${result.response.code()}: ${result.response.message()}")
}
//Result.Error and Result.Exception implement ErrorResultif (result isErrorResult) {
// Here yoy have an access to `exception` property of resultthrow result.exception
}
}
Nullable body
To prevent unexpected behavior with a nullable body of response Call<Body?>
extensions .await() and .awaitResult() are available only for
non-nullable Call<Body> or platform Call<Body!> body types:
funmain(args:Array<String>): Unit= runBlocking {
val user:Call<User> = api.getUser("username")
val userOrNull:Call<User?> = api.getUserOrNull("username")
// Doesn't work, because User is nullable// userOrNull.await()// Works for non-nullable typetry {
val result:User= user.await()
} catch (e:NullPointerException) {
// If body will be null you will get NullPointerException
}
// You can use .awaitResult() to catch possible problems with nullable bodyval nullableResult = api.getUser("username").awaitResult().getOrNull()
// But type of body should be non-nullable// api.getUserOrNull("username").awaitResult()// If you still want to use nullable body to clarify your api// use awaitResponse() instead:val responseBody:User?= userOrNull.awaitResponse().body()
}
Parallel requests
By wrapping call with kotlinx.coroutinesasync(), you may run a few requests parallelly without waiting for the previous request.
funmain(args:Array<String>): Unit= runBlocking {
val users =listOf("user1", "user2", "user3")
.map { username ->// Pass any coroutine context that fits better for your case// Coroutine Dispatcher also controls parallelism level // for CommonPool parallelism is `availableProcessors - 1`// But you can use any custom dispatcher with any parallelism strategy
async(CommonPool) {
// Send request. We use `awaitResult()` here to avoid try/catch, // but you can use `await()` and catch exceptions
api.getUser(username).awaitResult()
}
}
// Handle results// in this example we get result or null in case of error and filter all nulls
.mapNotNull {
// Wait (suspend) for result of `async()` and get result of request// We must call first `await()` only when all `async` blocks are created for parallel requests
it.await().getOrNull()
}
}
请发表评论