blob: c9c35826e610c13f5844f9de33a87084eaec3adc [file] [log] [blame]
Roman Elizarovf16fd272017-02-07 11:26:00 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Roman Elizarov3754f952017-01-18 20:47:54 +030017package kotlinx.coroutines.experimental
18
Roman Elizarovea4a51b2017-01-31 12:01:25 +030019import kotlin.coroutines.experimental.CoroutineContext
20import kotlin.coroutines.experimental.startCoroutine
Roman Elizarov3754f952017-01-18 20:47:54 +030021
22/**
23 * Deferred value is conceptually a non-blocking cancellable future.
24 * It is created with [defer] coroutine builder.
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030025 * It is in [active][isActive] state while the value is being computed.
Roman Elizarov3754f952017-01-18 20:47:54 +030026 */
27public interface Deferred<out T> : Job {
28 /**
29 * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete.
30 * This suspending function is cancellable.
31 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
Roman Elizarovc5814542017-01-19 10:19:06 +030032 * immediately resumes with [CancellationException].
Roman Elizarov3754f952017-01-18 20:47:54 +030033 */
34 public suspend fun await(): T
Roman Elizarovc5814542017-01-19 10:19:06 +030035
36 /**
37 * Returns *completed* result or throws [IllegalStateException] if this deferred value is still [isActive].
38 * It throws the corresponding exception if this deferred has completed exceptionally.
39 * This function is designed to be used from [onCompletion] handlers, when there is an absolute certainty that
40 * the value is already complete.
41 */
42 public fun getCompleted(): T
Roman Elizarov3754f952017-01-18 20:47:54 +030043}
44
45/**
46 * Starts new coroutine and returns its result as an implementation of [Deferred].
Roman Elizarov44ba4b12017-01-25 11:37:54 +030047 * The running coroutine is cancelled when the resulting object is [cancelled][Job.cancel].
48 *
49 * The [context] for the new coroutine must be explicitly specified.
Roman Elizaroved7b8642017-01-19 11:22:28 +030050 * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
Roman Elizarov44ba4b12017-01-25 11:37:54 +030051 * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
52 * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
Roman Elizarov3754f952017-01-18 20:47:54 +030053 */
Roman Elizarovd528e3e2017-01-23 15:40:05 +030054public fun <T> defer(context: CoroutineContext, block: suspend CoroutineScope.() -> T) : Deferred<T> =
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030055 DeferredCoroutine<T>(newCoroutineContext(context)).apply {
56 initParentJob(context[Job])
57 block.startCoroutine(this, this)
58 }
Roman Elizarov3754f952017-01-18 20:47:54 +030059
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030060internal open class DeferredCoroutine<T>(
61 context: CoroutineContext
62) : AbstractCoroutine<T>(context), Deferred<T> {
63 protected open fun start(): Boolean = false // LazyDeferredCoroutine overrides
Roman Elizarov58a7add2017-01-20 12:19:52 +030064
Roman Elizarov3754f952017-01-18 20:47:54 +030065 @Suppress("UNCHECKED_CAST")
66 suspend override fun await(): T {
67 // quick check if already complete (avoid extra object creation)
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030068 getState().let { state ->
69 if (state !is Active) {
70 if (state is CompletedExceptionally) throw state.exception
71 return state as T
72 }
73 }
74 if (start()) { // LazyDeferredCoroutine overrides
75 // recheck state (may have started & already completed
76 getState().let { state ->
77 if (state !is Active) {
78 if (state is CompletedExceptionally) throw state.exception
79 return state as T
80 }
81 }
Roman Elizarov3754f952017-01-18 20:47:54 +030082 }
83 // Note: await is cancellable itself!
84 return awaitGetValue()
85 }
86
87 @Suppress("UNCHECKED_CAST")
88 private suspend fun awaitGetValue(): T = suspendCancellableCoroutine { cont ->
89 cont.unregisterOnCompletion(onCompletion {
90 val state = getState()
91 check(state !is Active)
92 if (state is CompletedExceptionally)
93 cont.resumeWithException(state.exception)
94 else
95 cont.resume(state as T)
96 })
97 }
98
Roman Elizarovc5814542017-01-19 10:19:06 +030099 @Suppress("UNCHECKED_CAST")
100 override fun getCompleted(): T {
101 val state = getState()
102 check(state !is Active) { "This deferred value is still active" }
103 if (state is CompletedExceptionally) throw state.exception
104 return state as T
Roman Elizarov3754f952017-01-18 20:47:54 +0300105 }
Roman Elizarov2f6d7c92017-02-03 15:16:07 +0300106
107 // for nicer debugging
108 override fun toString(): String = "${javaClass.simpleName}{" +
109 (if (isActive) "isActive=true" else "completed=${getState()}") + "}"
Roman Elizarov3754f952017-01-18 20:47:54 +0300110}