Skip to content
This repository was archived by the owner on Jul 13, 2021. It is now read-only.

Commit 9ff2fc9

Browse files
elizarovcy6erGn0m
authored andcommitted
Introduce coroutines, replace promises with suspend funs
1 parent 3679f77 commit 9ff2fc9

File tree

11 files changed

+115
-92
lines changed

11 files changed

+115
-92
lines changed

frontend/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,9 @@ afterEvaluate {
6868
tasks.getByName("webpack-bundle") { dependsOn(copyResources) }
6969
tasks.getByName("webpack-run") { dependsOn(copyResources) }
7070
}
71+
72+
kotlin {
73+
experimental {
74+
coroutines "enable"
75+
}
76+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package kotlinx.coroutines.experimental
2+
3+
import kotlin.coroutines.experimental.*
4+
import kotlin.js.Promise
5+
6+
suspend fun <T> Promise<T>.await() = suspendCoroutine<T> { cont ->
7+
then({ value -> cont.resume(value) },
8+
{ exception -> cont.resumeWithException(exception) })
9+
}
10+
11+
fun <T> async(block: suspend () -> T): Promise<T> = Promise<T> { resolve, reject ->
12+
block.startCoroutine(object : Continuation<T> {
13+
override val context: CoroutineContext get() = EmptyCoroutineContext
14+
override fun resume(value: T) { resolve(value) }
15+
override fun resumeWithException(exception: Throwable) { reject(exception) }
16+
})
17+
}
18+
19+
fun launch(block: suspend () -> Unit) {
20+
async(block).catch { exception -> console.log("Failed with $exception") }
21+
}

frontend/src/org/jetbrains/demo/thinkter/Application.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.jetbrains.demo.thinkter.model.*
66
import react.*
77
import react.dom.*
88
import kotlin.browser.*
9+
import kotlinx.coroutines.experimental.async
910

1011
fun main(args: Array<String>) {
1112
runtime.wrappers.require("pure-blog.css")
@@ -134,14 +135,14 @@ class Application : ReactDOMComponent<ReactComponentNoProps, ApplicationPageStat
134135
}
135136

136137
private fun checkUserSession() {
137-
checkSession().then(
138-
{ user -> onUserAssigned(user) },
139-
{ t ->
140-
setState {
141-
selected = MainView.Home
142-
}
143-
}
144-
)
138+
async {
139+
val user = checkSession()
140+
onUserAssigned(user)
141+
}.catch {
142+
setState {
143+
selected = MainView.Home
144+
}
145+
}
145146
}
146147
}
147148

frontend/src/org/jetbrains/demo/thinkter/HomeView.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.html.*
44
import org.jetbrains.demo.thinkter.model.*
55
import react.*
66
import react.dom.*
7+
import kotlinx.coroutines.experimental.launch
78

89
class HomeView : ReactDOMComponent<HomeView.Props, HomeView.State>() {
910
companion object : ReactComponentSpec<HomeView, Props, State>
@@ -47,15 +48,15 @@ class HomeView : ReactDOMComponent<HomeView.Props, HomeView.State>() {
4748
}
4849

4950
private fun loadHome() {
50-
index().then({ r ->
51+
launch {
52+
val r = index()
5153
props.polling.start()
52-
5354
setState {
5455
loading = false
5556
top = r.top
5657
latest = r.latest
5758
}
58-
})
59+
}
5960
}
6061

6162
private val pollerHandler = { m : Polling.NewMessages ->

frontend/src/org/jetbrains/demo/thinkter/Login.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.jetbrains.demo.thinkter.model.*
77
import react.*
88
import react.dom.*
99
import kotlin.browser.*
10+
import kotlinx.coroutines.experimental.async
1011

1112
class LoginComponent : ReactDOMComponent<UserProps, LoginFormState>() {
1213
companion object : ReactComponentSpec<LoginComponent, UserProps, LoginFormState>
@@ -68,25 +69,24 @@ class LoginComponent : ReactDOMComponent<UserProps, LoginFormState>() {
6869
setState {
6970
disabled = true
7071
}
71-
72-
login(state.login, state.password).then(
73-
{ user -> loggedIn(user) },
74-
{ t -> loginFailed(t) }
75-
)
72+
async {
73+
val user = login(state.login, state.password)
74+
loggedIn(user)
75+
}.catch { err -> loginFailed(err) }
7676
}
7777

7878
private fun loggedIn(user: User) {
7979
props.userAssigned(user)
8080
}
8181

82-
private fun loginFailed(t: Throwable) {
83-
if (t is LoginOrRegisterFailedException) {
82+
private fun loginFailed(err: Throwable) {
83+
if (err is LoginOrRegisterFailedException) {
8484
setState {
8585
disabled = false
86-
errorMessage = t.message
86+
errorMessage = err.message
8787
}
8888
} else {
89-
console.error("Login failed", t)
89+
console.error("Login failed", err)
9090
setState {
9191
disabled = false
9292
errorMessage = "Login failed: please reload page and try again"

frontend/src/org/jetbrains/demo/thinkter/NavBarComponent.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import kotlinx.html.js.*
55
import org.jetbrains.demo.thinkter.model.*
66
import react.*
77
import react.dom.*
8+
import kotlinx.coroutines.experimental.launch
89

910
class NavBarComponent : ReactDOMComponent<NavBarComponent.NavBarHandlerProps, NavBarComponent.NavBarState>() {
1011

@@ -84,9 +85,10 @@ class NavBarComponent : ReactDOMComponent<NavBarComponent.NavBarHandlerProps, Na
8485
}
8586

8687
private fun logout() {
87-
logoutUser().then({
88+
launch {
89+
logoutUser()
8890
props.logoutHandler()
89-
})
91+
}
9092
}
9193

9294
private fun postNew() {

frontend/src/org/jetbrains/demo/thinkter/NewThoughtComponent.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.jetbrains.common.*
66
import org.jetbrains.demo.thinkter.model.*
77
import react.*
88
import react.dom.*
9+
import kotlinx.coroutines.experimental.async
910

1011
class NewThoughtComponent : ReactDOMComponent<NewThoughtComponent.Props, NewThoughtComponent.State>() {
1112
companion object : ReactComponentSpec<NewThoughtComponent, Props, State>
@@ -56,11 +57,11 @@ class NewThoughtComponent : ReactDOMComponent<NewThoughtComponent.Props, NewThou
5657
}
5758

5859
private fun doPostThought() {
59-
postThoughtPrepare().then({ t ->
60-
postThought(props.replyTo?.id, state.text, t).then({ thought ->
61-
onSubmitted(thought)
62-
}, { onFailed(it) }).catch { onFailed(it) }
63-
}, { onFailed(it) }).catch { onFailed(it) }
60+
async {
61+
val token = postThoughtPrepare()
62+
val thought = postThought(props.replyTo?.id, state.text, token)
63+
onSubmitted(thought)
64+
}.catch { err -> onFailed(err) }
6465
}
6566

6667
private fun onSubmitted(thought: Thought) {
@@ -71,9 +72,9 @@ class NewThoughtComponent : ReactDOMComponent<NewThoughtComponent.Props, NewThou
7172
props.showThought(thought)
7273
}
7374

74-
private fun onFailed(t: Throwable) {
75+
private fun onFailed(err: Throwable) {
7576
setState {
76-
errorMessage = t.message
77+
errorMessage = err.message
7778
}
7879
}
7980

frontend/src/org/jetbrains/demo/thinkter/Polling.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.jetbrains.demo.thinkter
22

33
import kotlin.browser.*
44
import kotlin.js.*
5+
import kotlinx.coroutines.experimental.launch
56

67
class Polling(val period: Int = 20000) {
78
private var timerId = 0
@@ -26,15 +27,15 @@ class Polling(val period: Int = 20000) {
2627
}
2728

2829
fun tick() {
29-
pollFromLastTime(lastTime.toString()).then({ newMessagesText ->
30+
launch {
31+
val newMessagesText = pollFromLastTime(lastTime.toString())
3032
val newMessages = when {
3133
newMessagesText == "0" || newMessagesText.isBlank() -> NewMessages.None
3234
newMessagesText.endsWith("+") -> NewMessages.MoreThan(newMessagesText.removeSuffix("+").toInt())
3335
else -> NewMessages.Few(newMessagesText.toInt())
3436
}
35-
3637
listeners.forEach { it(newMessages) }
37-
})
38+
}
3839
}
3940

4041
sealed class NewMessages {

frontend/src/org/jetbrains/demo/thinkter/Register.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.jetbrains.demo.thinkter.model.*
77
import react.*
88
import react.dom.*
99
import kotlin.browser.*
10+
import kotlinx.coroutines.experimental.async
1011

1112

1213
class RegisterComponent : ReactDOMComponent<UserProps, RegisterFormState>() {
@@ -93,25 +94,26 @@ class RegisterComponent : ReactDOMComponent<UserProps, RegisterFormState>() {
9394
setState {
9495
disabled = true
9596
}
96-
with(state) {
97-
register(login, password, displayName, email)
98-
.then({ user -> registered(user) })
99-
.catch { e -> registrationFailed(e) }
100-
}
97+
async {
98+
with(state) {
99+
val user = register(login, password, displayName, email)
100+
registered(user)
101+
}
102+
}.catch { err -> registrationFailed(err) }
101103
}
102104

103105
private fun registered(user: User) {
104106
props.userAssigned(user)
105107
}
106108

107-
private fun registrationFailed(e: Throwable) {
108-
if (e is LoginOrRegisterFailedException) {
109+
private fun registrationFailed(err: Throwable) {
110+
if (err is LoginOrRegisterFailedException) {
109111
setState {
110-
errorMessage = e.message
112+
errorMessage = err.message
111113
disabled = false
112114
}
113115
} else {
114-
console.log("Registration failed", e)
116+
console.log("Registration failed", err)
115117
setState {
116118
errorMessage = "Registration failed"
117119
}
Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,62 @@
11
package org.jetbrains.demo.thinkter
22

3+
import kotlinx.coroutines.experimental.await
34
import org.jetbrains.demo.thinkter.model.*
45
import org.w3c.dom.url.*
56
import org.w3c.fetch.*
67
import kotlin.browser.*
78
import kotlin.js.*
89

9-
fun index(): Promise<IndexResponse> {
10-
return getAndParseResult("/", null, ::parseIndexResponse)
11-
}
10+
suspend fun index(): IndexResponse =
11+
getAndParseResult("/", null, ::parseIndexResponse)
1212

13-
fun register(userId: String, password: String, displayName: String, email: String): Promise<User> {
14-
return postAndParseResult("/register", URLSearchParams().apply {
13+
suspend fun register(userId: String, password: String, displayName: String, email: String): User =
14+
postAndParseResult("/register", URLSearchParams().apply {
1515
append("userId", userId)
1616
append("password", password)
1717
append("displayName", displayName)
1818
append("email", email)
1919
}, ::parseLoginOrRegisterResponse)
20-
}
2120

22-
fun pollFromLastTime(lastTime: String = ""): Promise<String> {
23-
return getAndParseResult("/poll?lastTime=$lastTime", null, { json ->
21+
suspend fun pollFromLastTime(lastTime: String = ""): String =
22+
getAndParseResult<String>("/poll?lastTime=$lastTime", null, { json ->
2423
json.count
2524
})
26-
}
2725

28-
fun checkSession(): Promise<User> {
29-
return getAndParseResult("/login", null, ::parseLoginOrRegisterResponse)
30-
}
26+
suspend fun checkSession(): User =
27+
getAndParseResult("/login", null, ::parseLoginOrRegisterResponse)
3128

32-
fun login(userId: String, password: String): Promise<User> {
33-
return postAndParseResult("/login", URLSearchParams().apply {
29+
suspend fun login(userId: String, password: String): User =
30+
postAndParseResult("/login", URLSearchParams().apply {
3431
append("userId", userId)
3532
append("password", password)
3633
}, ::parseLoginOrRegisterResponse)
37-
}
3834

39-
fun postThoughtPrepare(): Promise<PostThoughtToken> {
40-
return getAndParseResult("/post-new", null, ::parseNewPostTokenResponse)
41-
}
35+
suspend fun postThoughtPrepare(): PostThoughtToken =
36+
getAndParseResult("/post-new", null, ::parseNewPostTokenResponse)
4237

43-
fun postThought(replyTo: Int?, text: String, token: PostThoughtToken): Promise<Thought> {
44-
return postAndParseResult("/post-new", URLSearchParams().apply {
38+
suspend fun postThought(replyTo: Int?, text: String, token: PostThoughtToken): Thought =
39+
postAndParseResult("/post-new", URLSearchParams().apply {
4540
append("text", text)
4641
append("date", token.date.toString())
4742
append("code", token.code)
4843
if (replyTo != null) {
4944
append("replyTo", replyTo.toString())
5045
}
5146
}, ::parsePostThoughtResponse)
52-
}
5347

54-
fun logoutUser(): Promise<Unit> {
55-
return window.fetch("/logout", object: RequestInit {
48+
suspend fun logoutUser() {
49+
window.fetch("/logout", object : RequestInit {
5650
override var method: String? = "POST"
5751
override var credentials: RequestCredentials? = "same-origin".asDynamic()
58-
}).then({ Unit })
52+
}).await()
5953
}
6054

61-
fun deleteThought(id: Int, date: Long, code: String): Promise<Unit> {
62-
return postAndParseResult("/thought/$id/delete", URLSearchParams().apply {
55+
suspend fun deleteThought(id: Int, date: Long, code: String) =
56+
postAndParseResult("/thought/$id/delete", URLSearchParams().apply {
6357
append("date", date.toString())
6458
append("code", code)
6559
}, { Unit })
66-
}
6760

6861
private fun parseIndexResponse(json: dynamic): IndexResponse {
6962
val top = json.top as Array<dynamic>
@@ -94,24 +87,18 @@ private fun parseLoginOrRegisterResponse(json: dynamic): User {
9487

9588
class LoginOrRegisterFailedException(message: String) : Throwable(message)
9689

97-
fun <T> postAndParseResult(url: String, body: dynamic, parse: (dynamic) -> T): Promise<T> {
98-
return requestAndParseResult("POST", url, body, parse)
99-
}
90+
suspend fun <T> postAndParseResult(url: String, body: dynamic, parse: (dynamic) -> T): T =
91+
requestAndParseResult("POST", url, body, parse)
10092

101-
fun <T> getAndParseResult(url: String, body: dynamic, parse: (dynamic) -> T): Promise<T> {
102-
return requestAndParseResult("GET", url, body, parse)
103-
}
93+
suspend fun <T> getAndParseResult(url: String, body: dynamic, parse: (dynamic) -> T): T =
94+
requestAndParseResult("GET", url, body, parse)
10495

105-
fun <T> requestAndParseResult(method: String, url: String, body: dynamic, parse: (dynamic) -> T): Promise<T> {
106-
return Promise { resolve, reject ->
107-
window.fetch(url, object: RequestInit {
108-
override var method: String? = method
109-
override var body: dynamic = body
110-
override var credentials: RequestCredentials? = "same-origin".asDynamic()
111-
override var headers: dynamic = json("Accept" to "application/json")
112-
113-
}).then({ response ->
114-
response.json().then({ resolve(parse(it)) }, reject).catch(reject)
115-
}, reject).catch(reject)
116-
}
117-
}
96+
suspend fun <T> requestAndParseResult(method: String, url: String, body: dynamic, parse: (dynamic) -> T): T {
97+
val response = window.fetch(url, object: RequestInit {
98+
override var method: String? = method
99+
override var body: dynamic = body
100+
override var credentials: RequestCredentials? = "same-origin".asDynamic()
101+
override var headers: dynamic = json("Accept" to "application/json")
102+
}).await()
103+
return parse(response.json().await())
104+
}

0 commit comments

Comments
 (0)