Omar Mainegra
08/06/2020, 8:51 PMSingle
to a suspended function? I couldn't find anything in coroutines-interop
module. Currently I'm doing
val body = suspendCoroutine<String?> { continuation ->
get("<https://postman-echo.com/get>", headers, query)
.subscribe(
onSuccess = { continuation.resume(it) },
onError = { continuation.resumeWithException(it) }
)
}
It works in JVM and JS, but in Native I get
kotlin.native.concurrent.FreezingException: freezing of $subscribe_2$<anonymous>_67$FUNCTION_REFERENCE$1621@e3e09f38 has failed, first blocker is EventLoopImpl@1c250dc8
Arkadii Ivanov
08/06/2020, 9:34 PMsuspend fun <T> Single<T>.suspendingGet(): T =
suspendCancellableCoroutine { continuation ->
val disposableWrapper = DisposableWrapper()
continuation.invokeOnCancellation { disposableWrapper.dispose() }
subscribe(
object : SingleObserver<T> {
override fun onSubscribe(disposable: Disposable) {
disposableWrapper.set(disposable)
}
override fun onSuccess(value: T) {
continuation.resume(value)
}
override fun onError(error: Throwable) {
continuation.resumeWithException(error)
}
}
)
}
Arkadii Ivanov
08/06/2020, 9:36 PMget(...)
is executed in background and so freezes the observer. AFAIK Continuation (as well as other things like Job, etc.) can not be frozen currently.Omar Mainegra
08/06/2020, 11:06 PMOmar Mainegra
08/06/2020, 11:11 PMblockingGet
Omar Mainegra
08/06/2020, 11:17 PM@Test
fun test_suspendingGet() = runAsyncTest {
val value = singleOf(1)
.delay(300, computationScheduler) // simulate a long running op
.suspendingGet()
assertEquals(1, value)
}
Arkadii Ivanov
08/07/2020, 6:37 AMArkadii Ivanov
08/07/2020, 6:40 AMArkadii Ivanov
08/07/2020, 6:45 AMArkadii Ivanov
08/07/2020, 7:33 AMclass MyLogic {
fun doSomething(): Single<Int> =
singleOf(1)
.delay(300L, computationScheduler)
}
class MyLogicTest {
private val logic = MyLogic()
private val testComputationScheduler = TestScheduler()
private val computationTimer = testComputationScheduler.timer
@BeforeTest
fun before() {
overrideSchedulers(computation = { testComputationScheduler })
}
@AfterTest
fun after() {
overrideSchedulers()
}
@Test
fun doSomething_returns_1_after_300_millis() {
val observer = logic.doSomething().test()
computationTimer.advanceBy(300L)
observer.assertSuccess(1)
}
@Test
fun doSomething_does_not_success_before_300_millis() {
val observer = logic.doSomething().test()
computationTimer.advanceBy(299L)
observer.assertNotSuccess()
}
}
Arkadii Ivanov
08/07/2020, 7:35 AMclass MyLogic(
private val delayScheduler: Scheduler
) {
fun doSomething(): Single<Int> =
singleOf(1)
.delay(300L, delayScheduler)
}
Arkadii Ivanov
08/07/2020, 7:35 AMOmar Mainegra
08/07/2020, 3:02 PMArkadii Ivanov
08/07/2020, 3:05 PMOmar Mainegra
08/07/2020, 3:06 PMSingle
to complete,Omar Mainegra
08/07/2020, 3:06 PMOmar Mainegra
08/07/2020, 3:07 PMexpect fun request(method: HttpMethod, url: String, headers: Headers, query: Query, body: RequestBody?): Single<ResponseBody>
Omar Mainegra
08/07/2020, 3:07 PM@Test
fun get_request() = runBlockingTest {
val body = withContext(Default) { get("<https://httpbin.org/get>", headers, query).suspendingGet() }
val echo = json.parse(PostmanEcho.serializer(), body)
checkHeaders(headers, echo)
checkQuery(query, echo)
}
Arkadii Ivanov
08/07/2020, 3:07 PMArkadii Ivanov
08/07/2020, 3:08 PMArkadii Ivanov
08/07/2020, 3:08 PMOmar Mainegra
08/07/2020, 3:09 PMArkadii Ivanov
08/07/2020, 3:10 PMOmar Mainegra
08/07/2020, 3:10 PMOmar Mainegra
08/07/2020, 3:10 PMArkadii Ivanov
08/07/2020, 3:10 PMOmar Mainegra
08/07/2020, 3:11 PMMockWebServer
only works on JVMArkadii Ivanov
08/07/2020, 3:12 PMArkadii Ivanov
08/07/2020, 3:12 PMArkadii Ivanov
08/07/2020, 3:12 PMArkadii Ivanov
08/07/2020, 3:12 PMOmar Mainegra
08/07/2020, 3:13 PMactual fun request(method: HttpMethod, url: String, headers: Headers, query: Query, body: RequestBody?): Single<ResponseBody> =
single { emitter ->
val urlComponent = NSURLComponents(string = url).apply {
queryItems = query.entries.map { (key, value) -> NSURLQueryItem(key, value) }
}
val request = NSMutableURLRequest.requestWithURL(urlComponent.URL!!).apply {
setHTTPMethod(method.name)
setAllHTTPHeaderFields(headers as Map<Any?, String>)
if (body != null) setHTTPBody((body as NSString).dataUsingEncoding(NSUTF8StringEncoding))
setCachePolicy(NSURLRequestReloadIgnoringCacheData)
}
val session = NSURLSession.sessionWithConfiguration(
configuration = NSURLSessionConfiguration.defaultSessionConfiguration(),
delegate = object : NSObject(), NSURLSessionDelegateProtocol {
override fun URLSession(
session: NSURLSession,
didReceiveChallenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit
) {
if (didReceiveChallenge.protectionSpace.host in trustedHosts) {
completionHandler(
NSURLSessionAuthChallengeUseCredential,
NSURLCredential.credentialForTrust(didReceiveChallenge.protectionSpace.serverTrust)
)
} else {
completionHandler(
NSURLSessionAuthChallengePerformDefaultHandling,
null
)
}
}
},
delegateQueue = NSOperationQueue.currentQueue
)
val callback: (NSData?, NSURLResponse?, NSError?) -> Unit = { data, _, error ->
if (data != null) emitter.onSuccess(NSString.create(data, NSUTF8StringEncoding) as String)
else emitter.onError(RequestError(error?.localizedDescription ?: "Unknown error"))
}
val task = session.dataTaskWithRequest(request, callback.freeze())
emitter.setDisposable(Disposable { task.cancel() })
task.resume()
}
Omar Mainegra
08/07/2020, 3:14 PM<http://httpbin.org|httpbin.org>
to testingOmar Mainegra
08/07/2020, 3:14 PMArkadii Ivanov
08/07/2020, 3:15 PMOmar Mainegra
08/07/2020, 3:15 PMfetch
and JSVM using OkHttp
Arkadii Ivanov
08/07/2020, 3:15 PMOmar Mainegra
08/07/2020, 3:16 PMArkadii Ivanov
08/07/2020, 3:38 PMOmar Mainegra
08/07/2020, 3:39 PMblockingGet
but it doesn't work in JSOmar Mainegra
08/07/2020, 3:41 PMOmar Mainegra
08/07/2020, 3:41 PMArkadii Ivanov
08/07/2020, 3:41 PMOmar Mainegra
08/07/2020, 3:43 PMOmar Mainegra
08/07/2020, 3:44 PM@Test
fun get_request() = runBlockingTest {
...
}
Arkadii Ivanov
08/07/2020, 3:44 PMArkadii Ivanov
08/07/2020, 3:45 PMrequest
looks like in JS?Omar Mainegra
08/07/2020, 3:45 PMOmar Mainegra
08/07/2020, 3:45 PMactual fun request(method: HttpMethod, url: String, headers: Headers, query: Query, body: RequestBody?): Single<ResponseBody> =
single { emitter ->
val queryStr = query.entries.joinToString("&") { (name, value) -> "$name=${encodeURIComponent(value)}" }
val urlWithQuery = "$url?$queryStr"
val headersObj = headers.entries.fold(js("({})")) { o, (name, value) -> o[name] = value; o }
val req = if (body != null) requestInit(method = method.name, headers = headersObj, body = body)
else requestInit(method = method.name, headers = headersObj)
window.fetch(urlWithQuery, req)
.then { it.text() }
.then { emitter.onSuccess(it) }
.catch { emitter.onError(it) }
}
Omar Mainegra
08/07/2020, 3:46 PMArkadii Ivanov
08/07/2020, 3:46 PMArkadii Ivanov
08/07/2020, 3:46 PMOmar Mainegra
08/07/2020, 3:46 PMOmar Mainegra
08/07/2020, 3:47 PMinternal fun requestInit(
method: String? = undefined,
headers: dynamic = undefined,
body: dynamic = undefined,
referrer: String? = undefined,
referrerPolicy: dynamic = undefined,
mode: RequestMode? = undefined,
credentials: RequestCredentials? = undefined,
cache: RequestCache? = undefined,
redirect: RequestRedirect? = undefined,
integrity: String? = undefined,
keepalive: Boolean? = undefined,
window: Any? = undefined
): RequestInit {
val o = js("({})")
o["method"] = method
o["headers"] = headers
o["body"] = body
o["referrer"] = referrer
o["referrerPolicy"] = referrerPolicy
o["mode"] = mode
o["credentials"] = credentials
o["cache"] = cache
o["redirect"] = redirect
o["integrity"] = integrity
o["keepalive"] = keepalive
o["window"] = window
return o
}
external fun encodeURIComponent(decodedURI: String): String
Omar Mainegra
08/07/2020, 3:50 PMenum class HttpMethod { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH }
typealias Headers = Map<String, String>
typealias Query = Map<String, String>
typealias RequestBody = String
typealias ResponseBody = String
Arkadii Ivanov
08/07/2020, 3:53 PMOmar Mainegra
08/07/2020, 3:54 PMactual fun runBlockingTest(block: suspend () -> Unit): dynamic = GlobalScope.promise { block() }
Arkadii Ivanov
08/07/2020, 3:55 PMArkadii Ivanov
08/07/2020, 3:56 PMexpect fun reaktiveTest(block: () -> Single<*>): Any
Omar Mainegra
08/07/2020, 3:57 PMPromise
in a way that is transparent (this is exactly what coroutines are doing)Arkadii Ivanov
08/07/2020, 3:58 PMOmar Mainegra
08/07/2020, 3:58 PMArkadii Ivanov
08/07/2020, 3:58 PMOmar Mainegra
08/07/2020, 3:58 PMArkadii Ivanov
08/07/2020, 3:59 PMOmar Mainegra
08/07/2020, 3:59 PMArkadii Ivanov
08/07/2020, 5:47 PMOmar Mainegra
08/07/2020, 5:49 PMOmar Mainegra
08/07/2020, 5:49 PMArkadii Ivanov
08/07/2020, 5:51 PMArkadii Ivanov
08/07/2020, 5:51 PMOmar Mainegra
08/07/2020, 5:52 PMArkadii Ivanov
08/07/2020, 6:43 PMfun <T> Single<T>.asPromise(): Promise<T> =
Promise { resolve, reject ->
subscribe(onSuccess = resolve, onError = reject)
}
expect fun Completable.testAwait()
class Tst {
@Test
fun foo() =
singleOf(1)
.observeOn(mainScheduler)
.asCompletable()
.testAwait()
}
JVM + Native
actual fun Completable.testAwait(): Unit = blockingAwait()
JS
actual fun Completable.testAwait(): dynamic =
asSingle(Unit).asPromise()
Omar Mainegra
08/07/2020, 6:44 PMdoOn..
should be enought?Omar Mainegra
08/07/2020, 6:45 PMdoOnAfterSuccess
Arkadii Ivanov
08/07/2020, 6:46 PMArkadii Ivanov
08/07/2020, 6:48 PMclass Tst {
@Test
fun foo() =
singleOf(1)
.observeOn(mainScheduler)
.doOnBeforeSuccess {
assertEquals(2, it)
}
.asCompletable()
.testAwait()
}
This worksOmar Mainegra
08/07/2020, 6:49 PMArkadii Ivanov
08/07/2020, 6:50 PMOmar Mainegra
08/07/2020, 6:50 PM