diff --git a/compiler/testData/codegen/box/coroutines/await.kt b/compiler/testData/codegen/box/coroutines/await.kt index 99d51454c01..093436eb720 100644 --- a/compiler/testData/codegen/box/coroutines/await.kt +++ b/compiler/testData/codegen/box/coroutines/await.kt @@ -54,7 +54,7 @@ private var log = "" private var inAwait = false -suspend fun await(value: Promise): S = CoroutineIntrinsics.suspendCoroutineOrReturn { continuation: Continuation -> +suspend fun await(value: Promise): S = suspendCoroutine { continuation -> if (inAwait) { throw IllegalStateException("Can't call await recursively") } diff --git a/js/js.libraries/src/core/coroutines.kt b/js/js.libraries/src/core/coroutines.kt index 9f6699f91c6..07551bd5066 100644 --- a/js/js.libraries/src/core/coroutines.kt +++ b/js/js.libraries/src/core/coroutines.kt @@ -73,6 +73,21 @@ public fun (suspend () -> T).startCoroutine( createCoroutine(completion, dispatcher).resume(Unit) } +/** + * Obtains the current continuation instance inside suspend functions and suspends + * currently running coroutine. + * + * In this function both [Continuation.resume] and [Continuation.resumeWithException] can be used either synchronously in + * the same stack-frame where suspension function is run or asynchronously later in the same thread or + * from a different thread of execution. Repeated invocation of any resume function produces [IllegalStateException]. + */ +@SinceKotlin("1.1") +public suspend fun suspendCoroutine(block: (Continuation) -> Unit): T = CoroutineIntrinsics.suspendCoroutineOrReturn { c -> + val safe = SafeContinuation(c) + block(safe) + safe.getResult() +} + // ------- internal stuff ------- internal interface DispatchedContinuation { @@ -151,4 +166,60 @@ private class ContinuationFacade(val innerContinuation: Continuation, val innerContinuation.resumeWithException(exception) } } -} \ No newline at end of file +} + +private val UNDECIDED: Any? = Any() +private val RESUMED: Any? = Any() +private class Fail(val exception: Throwable) + +internal class SafeContinuation internal constructor(private val delegate: Continuation) : Continuation { + private var result: Any? = UNDECIDED + + override fun resume(value: T) { + when (result) { + UNDECIDED -> { + result = value + } + CoroutineIntrinsics.SUSPENDED -> { + result = RESUMED + delegate.resume(value) + } + else -> { + throw IllegalStateException("Already resumed") + } + } + } + + override fun resumeWithException(exception: Throwable) { + when (result) { + UNDECIDED -> { + result = Fail(exception) + } + CoroutineIntrinsics.SUSPENDED -> { + result = RESUMED + delegate.resumeWithException(exception) + } + else -> { + throw IllegalStateException("Already resumed") + } + } + } + + internal fun getResult(): Any? { + if (result == UNDECIDED) { + result = CoroutineIntrinsics.SUSPENDED + } + val result = this.result + return when (result) { + RESUMED -> { + CoroutineIntrinsics.SUSPENDED // already called continuation, indicate SUSPENDED upstream + } + is Fail -> { + throw result.exception + } + else -> { + result // either SUSPENDED or data + } + } + } +}