On Asynchronicity in Blaze (again)

By Radosław Miernik · Published on · Comment on Meteor Forum, Reddit

Table of contents


Half a year ago, I left a list of a few open topics in On Asynchronicity in Blaze. While some stayed where they were, suggesting that the initial ideas were fine and nobody had issues with them, we made some progress overall. In this post, I’ll focus on what happened and what is being looked into.

However, consider this an extension of the previous article. If you have never heard of Blaze, I highly recommend at least skimming through it. It won’t be technical, but it may seem weird initially if you worked only with React or Vue.

Automatic awaiting in built-in blocks

So far, unwrapping of asynchronous values (Promises) has been supported only through #let blocks. While enough to handle all cases, it’s not really ergonomic. For example, since database operations in Meteor 3.0 are (will be) asynchronous, most #each blocks need to be wrapped in an additional #let:

<!-- Before: -->
{{#each user in getUsersSync}}
  Hi {{user.name}}!

<!-- After: -->
{{#let users=getUsersAsync}}
  {{#each user in users}}
    Hi {{user.name}}!

It was highly requested by the community, but I remained hesitant. You see, using #lets shows the intention and reminds you (I hope) to handle pending and rejected states. That’s what opinionated frameworks do, right?

At the same time, we’re talking about an established piece of software with huge applications built using it. For those making the transition (“async-ification”) even a little smoother is a big deal. It also stays in alignment with Meteor’s rapid prototyping mentality.

After a couple of discussions, we decided to make #each, #if, and #unless handle this case automatically: meteor/blaze#424. The only thing left was to decide what should happen on pending and rejected states.

Both #if and #unless are not rendering anything at all. Note that it’s NOT the same as a falsy value, which renders the {{else}} block (if any):

{{#if conditionAsync}}
  Resolved and truthy.
  Resolved and falsy.

The #each block is slightly different. At first, I wanted to keep it uniform with #if and #unless, but it’d be significantly more complex to implement; mostly because of the way it handles DOM modifications. Finally, I decided to treat them as empty sequences instead:

{{#each doc in sequenceAsync}}
  Resolved {{doc}}.
  Pending, rejected, or resolved and empty.

Automatic awaiting in attributes

Now, when almost all built-in blocks support async values (#with remains an exception since nobody’s asked for it so far; it’s kind of a niche anyway), what else doesn’t? Well, attributes! For example:

<img alt="{{getAltAsync}}" src="https://{{getSrcAsync}}">

It looks like those two are handled by the same piece of code, right? Well, it turned out it’s not. I won’t get into details here, but Blaze is smart enough to optimize the former to use the attribute directly, and the latter performs concatenation and some additional transformations on top.

Anyway, one rather complex function later, the majority of this functionality was handled. In short, the attributes are an object with potential Promises here and there, and we want to wait for all of them to resolve. If they won’t, we do nothing – just like #if and #unless.

Once I did that in meteor/blaze#428, it turned out that this logic allowed me to implement an additional feature almost for free! And since we all love getting more features for free…

Automatic awaiting in content

…I went for it. While the {{> await}} template we defined in the previous article comes in handy, it’s kind of verbose. What if we didn’t have to use it at all? What if it happened automatically? Like this:


It turned out to be simpler than I thought! However, it has a problem: it flickers. If you worked with React, you should know that creating components while rendering is not a good idea. The same thing happens here!

The way Blaze works is, in a way, very similar to React – a render function and some object managing lifecycle and state. To unwrap async values, we need to store a state somewhere. That’s why the above code creates an in-place View.

While extremely simple (eight lines of code), creating it leads to unnecessary mounts and unmounts. It’s not only an additional performance cost; it’s visible. That’s because when a new View is created and given a Promise, it cannot “look inside” of it synchronously – it has to .then it (even if it’s already resolved).

During the brief moment between unmounting of the previous View, and the execution of callback passed to .then (which is roughly one microtask long if nothing is hogging the event loop), the above template renders nothing. This is a known issue and may be improved in the future.

Closing thoughts

Blaze is evolving! There are still a few parts unaware of those changes (e.g., lifecycle handlers), but we’ll be looking into those soon, too. I would also like to thank Meteor Software and all my GitHub sponsors for funding my work on it!

Are we on the brink of Blaze’s renaissance? Maybe.