blob: ab61173f9909d3e5807c174fccfbe388dbd8b10d [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 Elizarovb7c46de2017-02-08 12:35:24 +030026 *
27 * Deferred value has four states:
28 *
29 * * _Active_ (initial state) -- [isActive] `true`, [isCompletedExceptionally] `false`,
30 * and [isCancelled] `false`.
31 * Both [getCompleted] and [getCompletionException] throw [IllegalStateException].
32 * * _Computed_ (final _completed_ state) -- [isActive] `false`,
33 * [isCompletedExceptionally] `false`, [isCancelled] `false`.
34 * * _Failed_ (final _completed_ state) -- [isActive] `false`,
35 * [isCompletedExceptionally] `true`, [isCancelled] `false`.
36 * * _Canceled_ (final _completed_ state) -- [isActive] `false`,
37 * [isCompletedExceptionally] `true`, [isCancelled] `true`.
Roman Elizarov3754f952017-01-18 20:47:54 +030038 */
39public interface Deferred<out T> : Job {
40 /**
Roman Elizarovb7c46de2017-02-08 12:35:24 +030041 * Returns `true` if computation of this deferred value has _completed exceptionally_ -- it had
42 * either _failed_ with exception during computation or was [cancelled][cancel].
43 * It implies that [isActive] is `false`.
44 */
45 val isCompletedExceptionally: Boolean
46
47 /**
48 * Returns `true` if computation of this deferred value was [cancelled][cancel].
49 * It implies that [isActive] is `false` and [isCompletedExceptionally] is `true`.
50 */
51 val isCancelled: Boolean
52
53 /**
Roman Elizarov3754f952017-01-18 20:47:54 +030054 * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete.
55 * This suspending function is cancellable.
56 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
Roman Elizarovc5814542017-01-19 10:19:06 +030057 * immediately resumes with [CancellationException].
Roman Elizarov3754f952017-01-18 20:47:54 +030058 */
59 public suspend fun await(): T
Roman Elizarovc5814542017-01-19 10:19:06 +030060
61 /**
62 * Returns *completed* result or throws [IllegalStateException] if this deferred value is still [isActive].
63 * It throws the corresponding exception if this deferred has completed exceptionally.
64 * This function is designed to be used from [onCompletion] handlers, when there is an absolute certainty that
65 * the value is already complete.
66 */
67 public fun getCompleted(): T
Roman Elizarov3754f952017-01-18 20:47:54 +030068}
69
70/**
71 * Starts new coroutine and returns its result as an implementation of [Deferred].
Roman Elizarov44ba4b12017-01-25 11:37:54 +030072 * The running coroutine is cancelled when the resulting object is [cancelled][Job.cancel].
73 *
74 * The [context] for the new coroutine must be explicitly specified.
Roman Elizaroved7b8642017-01-19 11:22:28 +030075 * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
Roman Elizarov44ba4b12017-01-25 11:37:54 +030076 * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
77 * 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 +030078 */
Roman Elizarovd528e3e2017-01-23 15:40:05 +030079public fun <T> defer(context: CoroutineContext, block: suspend CoroutineScope.() -> T) : Deferred<T> =
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030080 DeferredCoroutine<T>(newCoroutineContext(context)).apply {
81 initParentJob(context[Job])
82 block.startCoroutine(this, this)
83 }
Roman Elizarov3754f952017-01-18 20:47:54 +030084
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030085internal open class DeferredCoroutine<T>(
86 context: CoroutineContext
87) : AbstractCoroutine<T>(context), Deferred<T> {
88 protected open fun start(): Boolean = false // LazyDeferredCoroutine overrides
Roman Elizarov58a7add2017-01-20 12:19:52 +030089
Roman Elizarovb7c46de2017-02-08 12:35:24 +030090 override val isCompletedExceptionally: Boolean get() = getState() is CompletedExceptionally
91 override val isCancelled: Boolean get() = getState() is Cancelled
92
Roman Elizarov3754f952017-01-18 20:47:54 +030093 @Suppress("UNCHECKED_CAST")
94 suspend override fun await(): T {
95 // quick check if already complete (avoid extra object creation)
Roman Elizarov41c5c8b2017-01-25 13:37:15 +030096 getState().let { state ->
97 if (state !is Active) {
98 if (state is CompletedExceptionally) throw state.exception
99 return state as T
100 }
101 }
102 if (start()) { // LazyDeferredCoroutine overrides
103 // recheck state (may have started & already completed
104 getState().let { state ->
105 if (state !is Active) {
106 if (state is CompletedExceptionally) throw state.exception
107 return state as T
108 }
109 }
Roman Elizarov3754f952017-01-18 20:47:54 +0300110 }
111 // Note: await is cancellable itself!
112 return awaitGetValue()
113 }
114
115 @Suppress("UNCHECKED_CAST")
116 private suspend fun awaitGetValue(): T = suspendCancellableCoroutine { cont ->
117 cont.unregisterOnCompletion(onCompletion {
118 val state = getState()
119 check(state !is Active)
120 if (state is CompletedExceptionally)
121 cont.resumeWithException(state.exception)
122 else
123 cont.resume(state as T)
124 })
125 }
126
Roman Elizarovc5814542017-01-19 10:19:06 +0300127 @Suppress("UNCHECKED_CAST")
128 override fun getCompleted(): T {
129 val state = getState()
130 check(state !is Active) { "This deferred value is still active" }
131 if (state is CompletedExceptionally) throw state.exception
132 return state as T
Roman Elizarov3754f952017-01-18 20:47:54 +0300133 }
Roman Elizarov2f6d7c92017-02-03 15:16:07 +0300134
135 // for nicer debugging
136 override fun toString(): String = "${javaClass.simpleName}{" +
137 (if (isActive) "isActive=true" else "completed=${getState()}") + "}"
Roman Elizarov3754f952017-01-18 20:47:54 +0300138}