Wrapping up Firebase JS Promises with Coroutines & Flow
Apr 14, 2021

A Promise is the way you traditionally deal with asynchronous requests in the Javascript world.
With Kotlin-JS, here is how you would typically load an entry, using only the Firebase Javascript APIs:
|
|
This works fine™. The small problem with Promise
s is that they are specific to Javascript. If we hope to graduate to a Kotlin Multi-Platform setup one day, we can’t rely too much on them.
Thankfully, we can use Kotlin coroutines and the Flow
API to make Firebase’s callback-based APIs easier to use.
Stage 1: Chaining Promise
s
NOTE: In the first version of this article, I ended up focusing on using Coroutines to avoid ‘pyramid of doom’ callback situations. I’ve since realized this was something entirely avoidable with Promises. (I’m still somewhat of a JS newb after all.) As a result, I’ve edited this section. Many thanks to Tiger Oakes for his proofreading help.
Let’s start with a simple Promise chaining example. When initializing a hosted Firebase web project, your Firebase configuration object is automatically generated and made available under "/__/firebase/init.json"
.
Kotlin-JS exposes the Web Fetch API. It returns a Promise
to a Response
. Once the fetch call resolves, we then call .json()
on that Response
, which gives us another Promise
. When that Promise resolves, we get a Json
object that holds our config data.
Let’s see what this chaining looks like:
|
|
This is a tidy bit of code, but of course, we don’t really know when the callback will take place. It’s all within expectations, of course. The fetch call is async, and the Promise will eventually resolve and call back our firebaseInitApp()
function.
This becomes an issue for code that depends on firebaseInitApp()
having completed before it can run. Which, in our case, would be most of our web app. We end up having to weave our control flow into the Promise callback:
|
|
Let’s investigate how using a more sequential flow could make it easier to reason about our program.
Using Kotlin’s suspendCoroutine { }
In Kotlin, we have the Coroutines library available to us. We can use the suspendCoroutine {}
function to wrap our window.fetch()
call:
|
|

As you can see above, by using Kotlin Coroutines, we now have an idiomatic way to initialize and start our application sequentially. By keeping our control flow sequential like this, it becomes easier to reason about our code.
And, we can use this approach to wrap up Firebase DB calls too.
Suspending Firebase DB calls
In my last Kotlin-JS post, I showed you how to call on Firebase’s real-time DB APIs. Something like this:
|
|
Again we’re stuck with a callback type API. Converting that to a suspending function gives us:
|
|
But, while we’re at it, why not make a generic extension function out of this?
|
|
This covers most use cases of the Firebase get()
function, and also leverages Kotlin’s type-inference (see line 2) to keep things tidy.
Tying up loose ends with Flow
There’s one last use case I want to cover today. Firebase allows you to register handlers that trigger every time the data at the source is updated.
|
|
We can use the Kotlin Flow
APIs to give us a better… control flow:
|
|
Using Flow
here allows you to collect the data from a Coroutine scope when one of your components initializes. When your component lifecycle terminates, calling scope.cancel()
will automatically cancel all active collect {}
calls associated with a given components scope
.
That’s it for now!
In this article, we’ve seen how Coroutine
s can improve control flow in an application. Coroutines make our code easier to understand, letting us avoid ‘javascript callback hell’.
We’ve also covered how to convert Firebase callback-based APIs into a stream of information with Kotlin’s Flow
APIs.
There’s still a lot to explore with Kotlin-JS. For example, how to work with parallel asynchronous calls. That’s worth an article on its own. Watch this space for more soon!
You can find the backing code samples for this post in my Kotlin-JS Github project folder.
I’m also quite interested in getting feedback. Anything else you’d like to see me cover next? Questions, deep-dive requests, ideas for improvements? DM me on Twitter, and we’ll chat.
Thanks for your time, and stay safe!