blob: f9b9a60419d2ef967d653ac462316e1b472123ec [file] [log] [blame]
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("RedundantVisibilityModifier")
package kotlinx.coroutines.tasks
import com.google.android.gms.tasks.CancellationTokenSource
import com.google.android.gms.tasks.RuntimeExecutionException
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.TaskCompletionSource
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.*
/**
* Converts this deferred to the instance of [Task].
* If deferred is cancelled then resulting task will be cancelled as well.
*/
public fun <T> Deferred<T>.asTask(): Task<T> {
val cancellation = CancellationTokenSource()
val source = TaskCompletionSource<T>(cancellation.token)
invokeOnCompletion callback@{
if (it is CancellationException) {
cancellation.cancel()
return@callback
}
val t = getCompletionExceptionOrNull()
if (t == null) {
source.setResult(getCompleted())
} else {
source.setException(t as? Exception ?: RuntimeExecutionException(t))
}
}
return source.task
}
/**
* Converts this task to an instance of [Deferred].
* If task is cancelled then resulting deferred will be cancelled as well.
*/
public fun <T> Task<T>.asDeferred(): Deferred<T> {
if (isComplete) {
val e = exception
return if (e == null) {
@Suppress("UNCHECKED_CAST")
CompletableDeferred<T>().apply { if (isCanceled) cancel() else complete(result as T) }
} else {
CompletableDeferred<T>().apply { completeExceptionally(e) }
}
}
val result = CompletableDeferred<T>()
addOnCompleteListener {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
if (isCanceled) result.cancel() else result.complete(it.result as T)
} else {
result.completeExceptionally(e)
}
}
return result
}
/**
* Awaits for completion of the task without blocking a thread.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* stops waiting for the completion stage and immediately resumes with [CancellationException].
*/
public suspend fun <T> Task<T>.await(): T {
// fast path
if (isComplete) {
val e = exception
return if (e == null) {
if (isCanceled) {
throw CancellationException("Task $this was cancelled normally.")
} else {
@Suppress("UNCHECKED_CAST")
result as T
}
} else {
throw e
}
}
return suspendCancellableCoroutine { cont ->
addOnCompleteListener {
val e = exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
if (isCanceled) cont.cancel() else cont.resume(result as T)
} else {
cont.resumeWithException(e)
}
}
}
}