How to use Javascript Iterator Protocol from Kotlin

Hi Kotlin Community,

I’m about two months into my Kotlin MPP project and I am super happy with the multi plattform support and how well thought out Kotlin is and generally how well everything work. So, first of, HUGE thanks to the Kotlin team. I can finally do web programming without having to use Javascript!!

I was going to ask about how to use the JavaScript iterator protocol from kotlin but when I was writing this up it occurred to me how to do it so I will share it in the hope it’s useful to someone. Maybe the Kotlin Team can include this in the standard lib and / or docs :slight_smile:

So the problem was that I want the keys() from an URLSearchParam but the Kotlin JS Lib does not know that function. Luckily there is the external keyword that we can use to declare what is there in javascript:

/**
 * JavaScript Iterator Protocol (TM :))
 */
external interface JsIterator<T> {
    fun next() : JsIteratorResult<T>
}
external class JsIteratorResult<T> {
    val done : Boolean
    val value : T?
}

So this is the manual translation from the Mozilla reference and it just tells Kotlin what I expect the javascript objects to look like.

fun URLSearchParams.keys() : Iterable<String> {
    val keys = this.asDynamic().keys() ?: error("not keys() on $this")
    val jsIterator = keys as JsIterator<String>
    return jsIterator.iterable()
}

Here I am using the dynamic mechanism to call any method I want, in this case the JS method keys(). This can’t be checked by the Kotlin compiler so I add a checks of my own so that I’ll now right aways what’s up (ex: method not supported an other browser). I then cast to JsIterator which is a cast that will always succeed because JsIterator is external

fun <T> JsIterator<T>.iterable() : Iterable<T> {
    return object : Iterable<T> {
        override fun iterator(): Iterator<T> =
            object : Iterator<T> {
                private var elem = this@iterable.next()
                override fun hasNext() = !elem.done
                override fun next(): T {
                    val ret = elem.value ?: error("No more values")
                    elem = this@iterable.next()
                    return ret
                }
            }
    }
}

This just says how to translate from an JsIterator to a Kotlin Iterable because when you have an Iterable you can use all the cool extension functions like forEach()

    URL("...").searchParams.keys().forEach {
        println(it)
    }

This is what I really love about Kotlin: everything always comes together so nicely. Even if you have to deal with some ugliness like calling into JS type soup directly you can always contain it somewhere and keep the rest of your program looking nice and well-typed.

1 Like