Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.channels/ChannelIter

abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.flow/Flow { // kotlinx.coroutines.flow/Flow|null[0]
abstract suspend fun collect(kotlinx.coroutines.flow/FlowCollector<#A>) // kotlinx.coroutines.flow/Flow.collect|collect(kotlinx.coroutines.flow.FlowCollector<1:0>){}[0]

// Targets: [js]
open fun asAsyncIterable(): kotlinx.coroutines.internal/JsAsyncIterable<#A> // kotlinx.coroutines.flow/Flow.asAsyncIterable|asAsyncIterable(){}[0]

// Targets: [js]
final object Companion { // kotlinx.coroutines.flow/Flow.Companion|null[0]
final fun <#A2: kotlin/Any?> from(kotlin/Function0<kotlinx.coroutines.internal/JsAsyncIterator<#A2>>): kotlinx.coroutines.flow/Flow<#A2> // kotlinx.coroutines.flow/Flow.Companion.from|from(kotlin.Function0<kotlinx.coroutines.internal.JsAsyncIterator<0:0>>){0§<kotlin.Any?>}[0]
final fun <#A2: kotlin/Any?> from(kotlinx.coroutines.internal/JsAsyncIterable<#A2>): kotlinx.coroutines.flow/Flow<#A2> // kotlinx.coroutines.flow/Flow.Companion.from|from(kotlinx.coroutines.internal.JsAsyncIterable<0:0>){0§<kotlin.Any?>}[0]
final fun <#A2: kotlin/Any?> from(kotlinx.coroutines.internal/JsAsyncIterator<#A2>): kotlinx.coroutines.flow/Flow<#A2> // kotlinx.coroutines.flow/Flow.Companion.from|from(kotlinx.coroutines.internal.JsAsyncIterator<0:0>){0§<kotlin.Any?>}[0]
}
}

abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.flow/SharedFlow : kotlinx.coroutines.flow/Flow<#A> { // kotlinx.coroutines.flow/SharedFlow|null[0]
Expand Down Expand Up @@ -1263,7 +1273,7 @@ final inline fun <#A: kotlin/Any?> kotlinx.coroutines.internal/synchronized(kotl
final inline fun <#A: kotlin/Any?> kotlinx.coroutines.internal/synchronizedImpl(kotlinx.coroutines.internal/SynchronizedObject, kotlin/Function0<#A>): #A // kotlinx.coroutines.internal/synchronizedImpl|synchronizedImpl(kotlinx.coroutines.internal.SynchronizedObject;kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]

// Targets: [js]
abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.channels/ReceiveChannel : kotlinx.coroutines.channels/JsAsyncIterable<#A> { // kotlinx.coroutines.channels/ReceiveChannel|null[0]
abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.channels/ReceiveChannel : kotlinx.coroutines.internal/JsAsyncIterable<#A> { // kotlinx.coroutines.channels/ReceiveChannel|null[0]
abstract val isClosedForReceive // kotlinx.coroutines.channels/ReceiveChannel.isClosedForReceive|{}isClosedForReceive[0]
abstract fun <get-isClosedForReceive>(): kotlin/Boolean // kotlinx.coroutines.channels/ReceiveChannel.isClosedForReceive.<get-isClosedForReceive>|<get-isClosedForReceive>(){}[0]
abstract val isEmpty // kotlinx.coroutines.channels/ReceiveChannel.isEmpty|{}isEmpty[0]
Expand All @@ -1281,7 +1291,7 @@ abstract interface <#A: out kotlin/Any?> kotlinx.coroutines.channels/ReceiveChan
abstract fun tryReceive(): kotlinx.coroutines.channels/ChannelResult<#A> // kotlinx.coroutines.channels/ReceiveChannel.tryReceive|tryReceive(){}[0]
abstract suspend fun receive(): #A // kotlinx.coroutines.channels/ReceiveChannel.receive|receive(){}[0]
abstract suspend fun receiveCatching(): kotlinx.coroutines.channels/ChannelResult<#A> // kotlinx.coroutines.channels/ReceiveChannel.receiveCatching|receiveCatching(){}[0]
open fun asyncIterator(): kotlinx.coroutines.channels/JsAsyncIterator<#A> // kotlinx.coroutines.channels/ReceiveChannel.asyncIterator|asyncIterator(){}[0]
open fun asyncIterator(): kotlinx.coroutines.internal/JsAsyncIterator<#A> // kotlinx.coroutines.channels/ReceiveChannel.asyncIterator|asyncIterator(){}[0]
open fun cancel() // kotlinx.coroutines.channels/ReceiveChannel.cancel|cancel(){}[0]
open fun poll(): #A? // kotlinx.coroutines.channels/ReceiveChannel.poll|poll(){}[0]
open suspend fun receiveOrNull(): #A? // kotlinx.coroutines.channels/ReceiveChannel.receiveOrNull|receiveOrNull(){}[0]
Expand All @@ -1303,16 +1313,16 @@ final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutin
final fun <#A: kotlin/Any?> (kotlinx.coroutines/Deferred<#A>).kotlinx.coroutines/asPromise(): kotlin.js/Promise<#A> // kotlinx.coroutines/asPromise|asPromise@kotlinx.coroutines.Deferred<0:0>(){0§<kotlin.Any?>}[0]

// Targets: [js]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_copy_1tks5(noinline kotlinx.coroutines.channels/JsAsyncIterator<#A>, noinline kotlin/Function0<kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>> = ..., noinline kotlin/Function1<#A?, kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>> = ..., noinline kotlin/Function1<kotlin/Any?, kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>> = ...): kotlinx.coroutines.channels/JsAsyncIterator<#A> // kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_copy_1tks5|kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_copy_1tks5(kotlinx.coroutines.channels.JsAsyncIterator<0:0>;kotlin.Function0<kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>;kotlin.Function1<0:0?,kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>;kotlin.Function1<kotlin.Any?,kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>){0§<kotlin.Any?>}[0]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_copy_1tks5(noinline kotlinx.coroutines.internal/JsAsyncIterator<#A>, noinline kotlin/Function0<kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>> = ..., noinline kotlin/Function1<#A?, kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>> = ..., noinline kotlin/Function1<kotlin/Any?, kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>> = ...): kotlinx.coroutines.internal/JsAsyncIterator<#A> // kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_copy_1tks5|kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_copy_1tks5(kotlinx.coroutines.internal.JsAsyncIterator<0:0>;kotlin.Function0<kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>;kotlin.Function1<0:0?,kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>;kotlin.Function1<kotlin.Any?,kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>){0§<kotlin.Any?>}[0]

// Targets: [js]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_invoke_jkqnwo(noinline kotlin/Function0<kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>>, noinline kotlin/Function1<#A?, kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>>, noinline kotlin/Function1<kotlin/Any?, kotlin.js/Promise<kotlinx.coroutines.channels/JsIteratorResult<#A>>>): kotlinx.coroutines.channels/JsAsyncIterator<#A> // kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_invoke_jkqnwo|kotlinx_coroutines_channels_JsAsyncIterator_Companion_btfdib_invoke_jkqnwo(kotlin.Function0<kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>;kotlin.Function1<0:0?,kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>;kotlin.Function1<kotlin.Any?,kotlin.js.Promise<kotlinx.coroutines.channels.JsIteratorResult<0:0>>>){0§<kotlin.Any?>}[0]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_invoke_jkqnwo(noinline kotlin/Function0<kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>>, noinline kotlin/Function1<#A?, kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>>, noinline kotlin/Function1<kotlin/Any?, kotlin.js/Promise<kotlinx.coroutines.internal/JsIteratorResult<#A>>>): kotlinx.coroutines.internal/JsAsyncIterator<#A> // kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_invoke_jkqnwo|kotlinx_coroutines_internal_JsAsyncIterator_Companion_9orgqy_invoke_jkqnwo(kotlin.Function0<kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>;kotlin.Function1<0:0?,kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>;kotlin.Function1<kotlin.Any?,kotlin.js.Promise<kotlinx.coroutines.internal.JsIteratorResult<0:0>>>){0§<kotlin.Any?>}[0]

// Targets: [js]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_copy_1tks5(noinline kotlinx.coroutines.channels/JsIteratorResult<#A>, noinline #A? = ..., noinline kotlin/Boolean = ...): kotlinx.coroutines.channels/JsIteratorResult<#A> // kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_copy_1tks5|kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_copy_1tks5(kotlinx.coroutines.channels.JsIteratorResult<0:0>;0:0?;kotlin.Boolean){0§<kotlin.Any?>}[0]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_copy_1tks5(noinline kotlinx.coroutines.internal/JsIteratorResult<#A>, noinline #A? = ..., noinline kotlin/Boolean = ...): kotlinx.coroutines.internal/JsIteratorResult<#A> // kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_copy_1tks5|kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_copy_1tks5(kotlinx.coroutines.internal.JsIteratorResult<0:0>;0:0?;kotlin.Boolean){0§<kotlin.Any?>}[0]

// Targets: [js]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_invoke_jkqnwo(noinline #A? = ..., noinline kotlin/Boolean): kotlinx.coroutines.channels/JsIteratorResult<#A> // kotlinx.coroutines.channels/kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_invoke_jkqnwo|kotlinx_coroutines_channels_JsIteratorResult_Companion_6yc9qk_invoke_jkqnwo(0:0?;kotlin.Boolean){0§<kotlin.Any?>}[0]
final inline fun <#A: out kotlin/Any?> kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_invoke_jkqnwo(noinline #A? = ..., noinline kotlin/Boolean): kotlinx.coroutines.internal/JsIteratorResult<#A> // kotlinx.coroutines.internal/kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_invoke_jkqnwo|kotlinx_coroutines_internal_JsIteratorResult_Companion_kfjag7_invoke_jkqnwo(0:0?;kotlin.Boolean){0§<kotlin.Any?>}[0]

// Targets: [js]
final suspend fun (org.w3c.dom/Window).kotlinx.coroutines/awaitAnimationFrame(): kotlin/Double // kotlinx.coroutines/awaitAnimationFrame|awaitAnimationFrame@org.w3c.dom.Window(){}[0]
Expand Down
2 changes: 1 addition & 1 deletion kotlinx-coroutines-core/common/src/flow/Flow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ import kotlin.coroutines.*
* These implementations ensure that the context preservation property is not violated, and prevent most
* of the developer mistakes related to concurrency, inconsistent flow dispatchers, and cancellation.
*/
public interface Flow<out T> {
public expect interface Flow<out T> {

/**
* Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kotlinx.coroutines.flow

public actual interface Flow<out T> {
public actual suspend fun collect(collector: FlowCollector<T>)
}
28 changes: 4 additions & 24 deletions kotlinx-coroutines-core/js/src/channels/Channel.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
@file:OptIn(ExperimentalJsExport::class, ExperimentalStdlibApi::class)
@file:OptIn(ExperimentalJsExport::class)
@file:Suppress("EXPOSED_FUNCTION_RETURN_TYPE", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_INTERFACE")
package kotlinx.coroutines.channels

import kotlinx.coroutines.*
import kotlinx.coroutines.internal.JsAsyncIterable
import kotlinx.coroutines.internal.recoverStackTrace
import kotlinx.coroutines.selects.*
import kotlinx.js.JsPlainObject
import kotlin.internal.*
import kotlin.js.Promise
import kotlinx.coroutines.internal.JsAsyncIterator
import kotlinx.coroutines.internal.JsIteratorResult

@JsImplicitExport(couldBeConvertedToExplicitExport = true)
public actual interface ReceiveChannel<out E> : JsAsyncIterable<E> {
Expand Down Expand Up @@ -132,25 +134,3 @@ public actual interface ReceiveChannel<out E> : JsAsyncIterable<E> {
public actual val onReceiveOrNull: SelectClause1<E?> get() = (this as BufferedChannel<E>).onReceiveOrNull
}

@JsName("AsyncIterable")
internal external interface JsAsyncIterable<out T> {
@JsSymbol("asyncIterator")
public fun asyncIterator(): JsAsyncIterator<T>
}

@JsPlainObject
@JsName("AsyncIterator")
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols
internal external interface JsAsyncIterator<out T> {
public val next: () -> Promise<JsIteratorResult<T>>
// `return` and `throw` must be able to accept either zero arguments or a single one
public val `return`: (value: @UnsafeVariance T?) -> Promise<JsIteratorResult<T>>
public val `throw`: (value: Any?) -> Promise<JsIteratorResult<T>>
}

@JsPlainObject
@JsName("IteratorResult")
internal external interface JsIteratorResult<out T> {
public val value: T?
public val done: Boolean
}
112 changes: 112 additions & 0 deletions kotlinx-coroutines-core/js/src/flow/Flow.js.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@file:OptIn(ExperimentalJsExport::class, ExperimentalJsStatic::class, ExperimentalStdlibApi::class)
@file:Suppress("INVISIBLE_REFERENCE", "EXPOSED_FUNCTION_RETURN_TYPE", "EXPOSED_PARAMETER_TYPE")
package kotlinx.coroutines.flow

import kotlinx.coroutines.*
import kotlinx.coroutines.internal.JsAsyncIterable
import kotlinx.coroutines.internal.JsAsyncIterator
import kotlinx.coroutines.internal.JsIteratorResult
import kotlinx.coroutines.internal.JsOptionalExport
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.js.Promise

@JsOptionalExport(couldBeConvertedToExplicitExport = true)
public actual interface Flow<out T> {
@JsExport.Ignore
public actual suspend fun collect(collector: FlowCollector<T>)

/**
* Represents [Flow] as a JavaScript [AsyncIterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols)
*
* This function is a shorthand for:
* `buffer(0).produceIn(GlobalScope)`.
*
* Use it when a [Flow] needs to be exposed to JavaScript APIs that consume
* `AsyncIterable` (for example, via `for await (...)`).
*
* The returned iterable is backed by a coroutine started in [GlobalScope], so its lifecycle
* is not bound to a structured coroutine scope. With `buffer(0)`, elements are relayed with
* rendezvous-style backpressure (producer and consumer synchronize per element).
*
* Kotlin usage:
* ```
* val flow = flowOf(1, 2, 3)
* val asyncIterable = flow.asAsyncIterable()
* // pass asyncIterable to JS code expecting AsyncIterable
* ```
*
* JavaScript/TypeScript usage:
* ```javascript
* for await (const value of flow.asAsyncIterable()) {
* console.log(value)
* }
*```
*
* This API is experimental: behavior and lifecycle semantics may change in future releases.
*/
@ExperimentalCoroutinesApi
public fun asAsyncIterable(): JsAsyncIterable<T> =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This public function is missing a KDoc. In my opinion, in terms of describing the behaviors, it should be sufficient to specify that it's a shorthand for buffer(0).produceIn(GlobalScope) (that is, its implementation). A usage example and an explanation of when one needs to use this would be appropriate, too.

Also, this function should be either marked as @ExperimentalCoroutinesApi or hidden from Kotlin code. There are too many unclear aspects to it, and there is a potential for its behavior to change, so we can't call this a stable API.

buffer(0).produceIn(GlobalScope)

@JsExport.Ignore
// Important note: it would be much nicer to place those factory functions outside of Flow
// so from both Kotlin and TypeScript side it could be used without importing Flow (like in `flowOf` or `flow`)
// However, the described way of exporting factory functions forces the functions always to be exported (even if people don't use them and don't export Flow),
// and that may cause bundle size problems (at least right now).
// So, until the bundle size problem is solved, we keep those factory functions inside Flow, with possibility to move them outside later.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, since we know in advance those functions may need to get moved somewhere else, they need to be marked as experimental.

public companion object {
/**
* Converts a JavaScript AsyncIterable to a Kotlin Flow.
*
* The resulting flow will iterate through all values produced by the async iterable.
* If the flow collection is canceled or fails, the iterator's `return()` method will be called
* to properly clean up the async iterable.
*/
@JsStatic
@ExperimentalCoroutinesApi
public fun <T> from(async: JsAsyncIterable<T>): Flow<T> =
from(async.asyncIterator())

/**
* Converts a JavaScript async generator function to a Kotlin Flow.
*
* The generator will be invoked to get an async iterator for collection.
* Cancellation or failure during a collection triggers the iterator's `return()` method
* to ensure proper cleanup.
*/
@JsStatic
@JsName("fromAsyncGenerator")
@ExperimentalCoroutinesApi
public fun <T> from(generator: () -> JsAsyncIterator<T>): Flow<T> = flow {
var completed = false
val iterator = generator()
try {
while (true) {
val result = iterator.next().await()
if (result.done) {
completed = true
break
}
emit(result.value.unsafeCast<T>())
}
} finally {
Comment on lines +91 to +92

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not cancel the async iterator if emit threw an exception?

Note: in this scenario, it is fine to treat CancellationExceptions as a normal return, because that's consistent with the enormous design problem affecting the entirety of kotlinx.coroutines: #3658 We will need to fix the issue in the entire library later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it cancels the iterator, isn't it?

if (!completed) {
  iterator.asDynamic().`return`().unsafeCast<Promise<*>>().await()                
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you mean to provide the exception thrown by emit to the iterator via the throw method?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, exactly.

if (!completed) {
iterator.asDynamic().`return`().unsafeCast<Promise<*>>().await()
}
}
}

/**
* Converts a JavaScript AsyncIterator to a Kotlin Flow.
*
* The resulting flow emits items produced by the iterator until it reports completion.
* If a collection is canceled or fails, the iterator's `return()` method is called
* to close the iterator.
*/
@JsStatic
@JsName("fromAsyncIterator")
@ExperimentalCoroutinesApi
public fun <T> from(iterator: JsAsyncIterator<T>): Flow<T> =
from { iterator }
}
}
26 changes: 26 additions & 0 deletions kotlinx-coroutines-core/js/src/internal/JsAsyncIteratorProtocol.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@file:OptIn(ExperimentalStdlibApi::class)
package kotlinx.coroutines.internal

import kotlinx.js.JsPlainObject
import kotlin.js.Promise

@JsPlainObject
@JsName("AsyncIterator")
internal external interface JsAsyncIterator<out T> {
public val next: () -> Promise<JsIteratorResult<T>>
public val `return`: (value: @UnsafeVariance T?) -> Promise<JsIteratorResult<T>>
public val `throw`: (value: Any?) -> Promise<JsIteratorResult<T>>
}

@JsPlainObject
@JsName("IteratorResult")
internal external interface JsIteratorResult<out T> {
public val value: T?
public val done: Boolean
}

@JsName("AsyncIterable")
internal external interface JsAsyncIterable<out T> {
@JsSymbol("asyncIterator")
public fun asyncIterator(): JsAsyncIterator<T>
}
13 changes: 3 additions & 10 deletions kotlinx-coroutines-core/js/test/ChannelInteropTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package kotlinx.coroutines

import kotlinx.coroutines.channels.*
import kotlinx.coroutines.internal.JsAsyncIterator
import kotlinx.coroutines.internal.JsIteratorResult
import kotlinx.coroutines.internal.assertNextStepToBe
import kotlinx.coroutines.testing.*
import kotlin.js.*
import kotlin.test.*
Expand Down Expand Up @@ -195,14 +198,4 @@ class ChannelInteropTest : TestBase() {
assertNextStepToBe(iterator, done = true)
assertEquals(2, channel.receive())
}

private suspend fun <T> assertNextStepToBe(
iterator: JsAsyncIterator<T>,
value: T? = js("undefined"),
done: Boolean = false
) {
val result = iterator.next().await()
assertEquals(done, result.done)
assertEquals(value, result.value)
}
}
Loading