Why are the Firebase API asynchronous?

The engineers on the Firebase SDK teams put great effort into making their APIs consistent and easy to use. One thing you may have noticed with the mobile client SDKs is that all of the API calls that deal with reading and writing data are fully asynchronous. This means that the call always returns immediately, without blocking the code to wait for a result. The results come some time later, whenever they’re ready.

In JavaScript, these asynchronous methods usually return a Promise. For Android, the method will return a Task object, which is very similar to a promise. And for iOS, you’ll pass a completion block (“closure” in Swift) that’ll be invoked later to handle the result.

The thing about asynchronous programming is that it’s not really intuitive at first. If you want to fetch some data, it’s natural to want to write code that’s structured something like this:

try {
result = database.get("the_thing_i_want")
// handle the results here
}
catch (error) {
// handle any errors here
}

This is a synchronous call, and it’s short and easy to understand. The result of get() is being returned directly from the function, and the calling code is waiting for it to complete. But this is precisely the problem. You don’t want your code to stop to wait for something that could take a long time.

All those times when you’ve had a terrible mobile connection and waited forever for it to load.

You can think about Firebase asynchronous APIs the same way you think about using an oven. If you want to bake a cake, you typically don’t stand at the oven waiting for it to finish. Nobody interacts with their oven like a synchronous API! Instead, you set a timer on the oven, and it tells you asynchronously when the work is done, while you go and do other things. So much more efficient with your time, right?

So, what does an asynchronous call look like with Firebase? Let’s use the Cloud Firestore API, for example. Fetching a document requires a network connection or a local disk cache, and there are no guarantees about the speed of either of them. So, the document fetch API must be called like this:

Android/Java:

Task<DocumentSnapshot> task =
FirebaseFirestore.getInstance().document("users/pat").get();
task.addOnSuccessListener(new OnSuccessListener() {
public void onSuccess(DocumentSnapshot snapshot) {
// handle the document snapshot here
}
});
task.addOnFailureListener(new OnFailureListener() {
public void onFailure(Exception e) {
// handle any errors here
}
});

web/JavaScript:

var promise = firebase.firestore().doc("users/pat").get();
promise.then(snapshot => {
// handle the document snapshot here
})
.catch(error => {
// handle any errors here
});

iOS/Swift:

Firestore.firestore().document("users/pat")
.getDocument() { (snapshot, err) in
if let snapshot = snapshot {
// handle the document snapshot here
}
else {
// handle any errors here
}
}

Well, that’s a bit of extra typing compared to the simpler synchronous prototype!

So, why make the consumer of an API go through the extra trouble of using an asynchronous API? Can’t we just have simpler synchronous APIs instead? Well, Firebase could provide synchronous APIs for mobile apps, but then we’d inherit one of two problems that are both even worse than writing this extra code:

  1. Calling a synchronous function on your app’s main thread could freeze the app indefinitely, which is a terrible user experience. On Android, it could also soft-crash with an Application Not Responding (ANR) dialog.
  2. To avoid blocking the main thread with a synchronous method, we’d have to manage our own threads to call these APIs properly. This is even more code, and can be difficult to get right. Even expert engineers have challenges with correct threading behavior.

If problem #1 is unclear, I’ll take a moment to say more about that in the next section.

It’s worth noting that there are also a couple benefits to using an asynchronous API:

  1. The API provider has the ability to optimize the threading behavior in a way that you might not be able to duplicate yourself. (You should trust the API provider to understand the performance characteristics of their work!)
  2. It opens the door for future enhancements, such as the cancellation of ongoing work.

If you ask me, I’d rather have an asynchronous API that manages all the required threading behind the scenes. With this, my app becomes a lot easier to write.

Why is blocking the main thread so bad?

Application runtime environments that have a user interface typically drive all interactions on that UI through a single thread called the “main thread”. A vast majority of application code you write will execute on that thread. The thread is controlled by an event loop, which basically churns through a queue of work items forever, until the app’s process dies. These items of work include handling events, rendering to screen, animations, and anything that has to do with the UI. It has to cycle through these items of work fast enough so that it can also render the screen at a smooth 60 frames per second. That means an item of work has, at most, 16ms to complete. Any more than that, and the main thread has skip the rendering of the UI for your app until that work is finally done. Skipping frames is not good!

So, if one of those bits of work in the event loop takes too long, it’ll get stuck in the queue, and the app will appear “janky” or even completely stuck, even if it’s still doing something! It’s critical to make sure your work on the main thread never blocks, no matter what the reason, so your UI is always smooth and responsive. Don’t be like this:

Your app’s event loop can’t be blocked up with work that takes so long that it drops frames. The consequences are bad ratings for your app because of poor user experience.

But it doesn’t work the way I want!

Let’s take those examples above, ignore the error cases for now, and add some code. Each example has three log lines, numbered 1, 2 and 3, that prints the value of the firstName variable. Pick the language you prefer and try to guess the log output. Log lines are in bold.

Android/Java:

private mFirstName = "unknown";  // defined as a class member
Log.d("DEBUG", "1. firstName = "+ mFirstName);
Task<DocumentSnapshot> task =
FirebaseFirestore.getInstance().document("users/bob").get();
task.addOnSuccessListener(new OnSuccessListener() {
public void onSuccess(DocumentSnapshot snapshot) {
mFirstName = snapshot.getString("firstName")
Log.d("DEBUG", "2. firstName = "+ mFirstName);
}
});
Log.d("DEBUG", "3. firstName = "+ mFirstName);

web/JavaScript:

var firstName = "unknown"
console.log("1. firstName = " + firstName);
var promise = firebase.firestore().doc("users/pat").get();
promise.then(snapshot => {
firstName = snapshot.get("firstName");
console.log("2. firstName = "+ firstName);
})
console.log("3. firstName = "+ firstName);

iOS/Swift:

var firstName = "unknown"
print("1. firstName = \(firstName)")
Firestore.firestore().document("users/pat")
.getDocument() { (snapshot, err) in
firstName = snapshot!.data()!["firstName"] as! String
print("2. firstName = \(firstName)")
}
print("3. firstName = \(firstName)")

So, what do you think the output will be, if the firstName property of the doc is “Pat”?

Your first impression might be something like this:

1. name = unknown
2. name = Pat
3. name = Pat

After all, many programs execute lines of code in the order they appear. However, remember that the document fetch method get() (or getDocument()), is asynchronous, and it returns immediately, before the callback that handles the snapshot is invoked. Then, that callback will be invoked later on the main thread, so it can safely update the UI if necessary. This means the log #3 actually executes immediately after #1, before the callback is invoked with #2, like this:

1. name = unknown
3. name = unknown
2. name = Pat

Why is #3 showing a value of “unknown”? Because it’s executing before the callback where #2 is logged. This result can be confusing if you’re not thinking asynchronously!

It’s important to take note of which Firebase APIs are asynchronous. It’s easy to spot them because their API docs show that they don’t directly return the data you want. Instead, they’ll return things like tasks and promises, or take completion blocks or listeners. You’ll need to use these mechanisms to receive the results of the calls. (To find out when the oven baking is done, to repeat the prior analogy.)

To learn about the Task API used on Android, I have an extended guide for that on the Firebase blog. JavaScript promises have a lot of documentation and examples that you can find through web searches. And Swift completion blocks are also very well documented by Apple.

And definitely be sure to use the Firebase API reference to learn about the Firebase APIs you want to use before you actually use them, so you can figure out which calls are asynchronous.

Think async!

Firebase APIs are sensitive to the performance of your app’s main thread. This means that any Firebase API that needs to deal with data on disk or network is implemented in an asynchronous style. The functions will return immediately, so you can call them on the main thread without worrying about performance. All you have to do is implement callbacks using the patterns established in the documentation and samples.

If you have a programming question about Firebase APIs, please don’t hesitate to ask those on Stack Overflow with the “firebase” tag. And be sure to learn how to ask a good question before posting your question.

For further consideration

Astute readers may be aware that Kotlin makes the Android Task API a lot cleaner to work with than Java. Here’s the first Java sample from above converted to Kotlin. Compare for yourself how much code was saved here:

val task =
FirebaseFirestore.getInstance().document("users/pat").get()
task.addOnSuccessListener { snapshot ->
// handle the document snapshot here
}
task.addOnFailureListener { e ->
// handle any errors here
}

Also, if you’re a JavaScript programmer, you may be aware of the new async/await syntax available in ECMAScript 2017 and TypeScript. This makes the JavaScript sample look synchronous, even though it isn’t. See how much cleaner it looks when converted:

try {
const doc = await firebase.firestore().doc("users/pat").get();
// handle the document snapshot here
}
catch(e) {
// handle any errors here
}

This looks a lot like the original synchronous example, doesn’t it?

(Note that this only works if the enclosing function is declared “async”, which means the return value from it will be the promise coming from the final await in that function.)