Blaze: Problem with async helpers and async template hooks

Hello!

We’ve started refactoring our code for it to be compatible with Meteor 3.0, and part of it is moving the server code from the old sync Mongo API to the new async Mongo API. For example, findOne will now be findOneAsync. To keep the flow of our code working as intended, we must use await when calling these async functions and, therefore, the functions in which they are called must be made async.

This is only required on the server. However, for the sake isomorphism (keeping the code similar everywhere) we also wanted to refactor our client code. However, while doing this, we ran into two problems:

Firstly, for helpers to be async, they must be called using let, otherwise [object Promise] is rendered. This works, but makes the syntax of calling a helper way more complex. Also, it seems some Blaze functions just don’t work in async helpers.

Secondly, we ran into problems for the Template onCreated and onRendered callbacks. Making onCreated async makes it so that onRendered can be called before the onCreated finishes running, which can break some of our logic. Some workarounds can be made so onRendered awaits for onCreated, but these don’t seem like a good solution.

We would really like to refactor our client to keep the code similar between client and server, but these issues would make the code more complex than just keeping the code different between them.

Is there something we are missing that could solve these problems, or would we just need to deal with the added complexity in the client code? Also, what are you doing about it in your 3.0 projects? Are you keeping the code similar between server and client despite these issues or did you prefer to keep them different so the client code wouldn’t become much more complex?

Thank you for your attention! :wink:

I use Svelte but I think the approach below is frontend agnostic.

I use sync for reads on the client, i.e. .find.fetch and .findOne since I use minimongo as my source of truth.

For Meteor.methods, I use *Async to keep code isomorphic for optimistic ui.

On the server, everything uses *Async.

2 Likes

@float07 do you rely on strict isomorphism? Because if not, you can safely remain with the sync mongo methods on the client. Saved me a lot of headaches lately

2 Likes

@jam thanks for sharing your approach with me!

@jkuester no I don’t, but it would be nice to be able to keep the code similar between client and server. Less complexity and less things to think about when coding. It’s not a big deal if we can’t do that. It’s just something we would like to have.

For now it seems that the best approach will be to keep using the sync Mongo API in the client. Anyone have any idea if there are plans to make it easier to deal with async code in these scenarios I mentioned in the client?

If that’s the case, please file a bug — it was added in Implemented async attributes and content. by radekmie · Pull Request #428 · meteor/blaze · GitHub.

I missed this thread, but this can be solved in two ways:

  1. Store the Template.instance() before the first await and reference it later (as proposed in this thread).
  2. Create a helper similar to Tracker.withComputation added in Implemented async Tracker with explicit values. by radekmie · Pull Request #12294 · meteor/meteor · GitHub since it’s the same problem — keeping global state between awaits.

I remember discussing it with someone but don’t remember why we finally didn’t. I think it’s double doable, though.

Just make Blaze even better, that’s what we did with async :stuck_out_tongue:

I spent some effort converting client code to async for the sake of isomorphism, too, but had to roll a lot of it back. One big reason was that an await operator causes a return to the event loop, which means the ordering of code running changes. That exposed a lot of questionable code that depended on things happening in a certain order, and it wasn’t worth it to rework it to be more robust.

2 Likes

Initially we had the same plans, but have decided to keep ‘pure’ client code as is to indeed avoid the issues you mentioned. With ‘pure’ I mean everything user interface oriented, only those pieces of code where we really want to have code run on server and client side (the optimistic updates) when doing a back end call. Many previously ‘shared’ server calls are being moved to ‘server’ only (effect is that you client code has to wait for the round trip of the data via the subscriptions - but that’s not always that bad).

Async in OnCreated we already encountered e,g. when dealing with video/audio setups, the simple solution there is to let the OnRender wait on an Reactive variable that indicates end of OnCreated (which typically only happens once). If only used in those ‘exceptional cases’ , it’s acceptable.

We have been idle for a while on Meteor 3.0 refactoring but will start on it again shortly. I’d like more people to post their experiences (as we have also been doing and will do again when needed).

Thank you all for sharing, and sorry it took me a while to get back to this post.

After talking with my team, we decided to drop isomorphism for now, and let the client and server code be different regarding async and await. This means we will use the async versions of functions in the server, since they are needed, but we will use the sync versions on client.
We will keep this decision until we are sure the client issues mentioned won’t be a problem for us (when they get fixed or when we find a good workaround).

@radekmie

If that’s the case, please file a bug

Will do! Thanks for letting me know :smiley:

I remember discussing it with someone but don’t remember why we finally didn’t. I think it’s double, though.

I’m sorry, I didn’t quite understand what you meant here. Can you elaborate, please? What you mean by “we finally didn’t” and “double”? Thanks

To make it a bit easier in handling different code in a potential isomorphic setup you can leverage meteor’s exact code splitting at build time:

1 Like

I meant that there were some internal discussions about supporting async in onCreated and onRendered, but it wasn’t followed up on.

And “double” → “doable” (I already fixed that).

1 Like