I sometimes get this error on the front end. However I am not setting timeouts anywhere. I am using the new async methods inside Meteor.methods.
Basically the only method I have is this (on both sides, but I’ve learned that Meteor.methods are no longer good for simulations and isomorphic code like old code used to be before non-async methods were removed):
In the past, we were able to write isomorphic methods very easily. Now we are not, and the problem I have is probably because I should no longer have the method on the client-side (no longer isomorphic).
Yes, but not isomorphic reactive code. That’s been removed, which makes the development experience not as good as before: reactivity is the magic that Meteor gave us, and part of that magic is now replaced with isomorphic promise-based non-reactive code.
If your method does not set timers, and you do not see the warning Method stub (<method name>) took too long and could cause unexpected problems., this should be impossible. If you are still seeing this error, it is a little concerning.
One thing you could try is setting a breakpoint on the line that throws the error. When you next see the error, it would be helpful to know what the stack trace is. Specifically, why is the code running - was some callback or event run during a method simulation, or is it actually related somehow to the method?
and we could use this method on both the server and the client isomorphically. But! Additionally, we were also able to do the following on the client, and it would trigger reactivity as expected:
Tracker.autorun(() => {
// any time the `Books` collection is updated, this
// will re-run the autorun, logging the number of books,
// and the number of authors
Meteor.call('someMethod')
})
Now, because Meteor recently removed the synchronous collection APIs from backend, methods have to be written like so:
Although the new method using await is still isomorphic meaning it can be called on both server and client, it is no longer reactively isomorphic: if you try to write the same Tracker.autorun as the previous sample,
it no longer works as expected! If the number of authors changes, but the number of books does not, the autorun will not rerun because after the first await call, the effect (the computation in Meteor terms) is no longer able to track all the dependencies after the await call.
This means that isomorphism is still in place but reactivity is not.
To work around this, one has two options:
1) use Tracker.withComputation and make the code a little messier but also may introduce reactive race conditions because effects can now be interleaved with each other in a way that isn’t as easy to understand as synchronous effects.
2) or write separate methods for server vs client (no longer isomorphic). For example:
Note, we could name them the someMethod on both sides, but it might be unexpected and confusing to have different behavior on one side vs the other.
The ideal would be to have a single way of defining methods on server and client, and for it to work the same way on both sides (reactively, without Fibers). To learn how this is possible, recommend studying Solid.js and its current createResource API. The createResource API creates a signal that is connected to an async process (could be network requests for example) that can then be used in synchronous effects functions. There are other patterns too like what I described above, and Solid.js is also coming out with a new createAsync API in Solid 2.0 as alternative to createResource.
It is possible we can start to build similar primitives on top of Meteor’s signals and effects (Tracker+ReactiveVar) to achieve similar patterns, and use them as an alternative to Meteor.methods. First we’d have to ensure we update Tracker and related APIs to be server-compatible (the docs currently mention they are client-only), that way we can write the same reactive code on both sides.
Yep, that’s what I’m seeing (not the error you mentioned, only the one I wrote above).
Maybe I was calling the method when it was still awaiting? I refactored the code already and haven’t had the issue since (moving fast). I’ll aim for a reproduction if it happens again.
Thanks! I honestly had no idea that was a thing previously. I assumed people used pub-sub if they wanted to read reactive updates to a collection(s).
Seems kind of like a potential foot gun but maybe I’m missing something. Would be curious to hear of real world scenarios where people are / were using this pattern and why it’s preferred over pub-sub:
These are tangential. Pub-sub is still required for exposing the data to the client, but the method would separately (before the Promise-based APIs) be reactive on the client (if the data is present due to the pubsub).
Now, after the APIs have been changed to promises, pubsub still does the same thing as before and exposes the data to the client, but the difference in how the methods works changed as described above regardless of the existence of pubsub.
Pubsub pushes server data into collection, and then separately Meteor methods can read that data, and this change only affects the latter (pubsub being required either way).
Hopefully that cleared up the question. The two things, pubsub and meteor methods, are two separate things, and only the latter has been affected but the way you use pubsub still remains the same and does not affect how you use methods.
Another way to put it is, without pubsub, both pre-promise Meteor, and post-promise Meteor, would have no way to expose data to methods running on the client (doesn’t matter if the methods are async or not, pubsub is required either way (without the autopublish package for prototyping)).
Would a workaround be to make a synchronous query in the autorun callback on the relevant collection and use that to trigger the necessary Meteor method call?
Not as convenient mind you. But maybe something that’s a low-cost op on the client would be good enough.
(I haven’t tested this and I’m embarrassed to say I haven’t been working much with pub sub recently!)
I see what you’re saying but I still struggle with a real world scenario where wrapping a method in Tracker.autorun would be super useful. I suppose it’s because I’m used to using pub-sub for my reads and methods for my writes so everything remains reactive and isomorphic. Maybe you or someone else can enlighten me.
For the specific example you gave above I’d do this on the svelte frontend instead of having someMethod:
It’s “signals and effects”. Once one knows the power of signals and effect patterns, they’ll realize the wonder of being able to wrap a function in an effect. This is what we easily had in older Meteor.
I’m not sure what are some good articles to point you to for signals and effects patterns, but diving into Solid.js might help. Patterns across the ecosystem have adopted Signals and Effects (something Meteor has had for ages, but it was named autorun (computations) and reactive vars rather than effects and signals) because people are realizing how powerful yet simple they are.
I’m not quite sure what you mean. Subscriptions (Meteor.subscribe) are not for reading, but for requesting certain data be synced to the client. The way that you read that data is via the Collections API, or Meteor.methods. Collections and Meteor.methods are also used for writing (but you cannot use Collections or Meteor.methods without first calling subscribe so that data will become available for reading).
Once you’ve enabled certain data via Meteor.publish (backend) and Meteor.subscribe (client) then you can use methods. This has been true since the beginning of Meteor time. The only difference between then, and now, is that Meteor.methods are no longer easily isomorphic while still being reactive (publications and subcriptions have no bearing on that).
Yes, that works. I think you may be missing what I’m saying because perhaps it is something you haven’t tried before? What I’m describing is that (in the past, before the promise-based APIs, while we were on synchronous-style Fibers code), a Meteor.method was basically “just a function” and it didn’t matter if you called it on backend or frontend, and it also (this is the key) worked inside Tracker.autorun (or with Svelte integration, inside $m blocks if you look at it that way).
In the past, it was easy to have an isomorphic function like this:
Meteor.methods({
// This function is used in both the server and the client in various places (in pre-Promise Meteor)
stats() {
return `There are ${Books.find({}).count())} books and ${Authors.find({}).count())} authors.`
}
})
where this “stats” function could be called on the server or the client, and additionally if it was called on the client it could be used reactively. That was a great thing, and that’s what is now missing (or it is now more difficult to do, or requires splitting code up into separate server vs client pieces).
That’s only a simple example. Imagine an app with large amounts of reactive isomorphic “just function” code that uses the code on both backend and frontend. It will be a pain to refactor into modern Meteor.
Also, I want to write this type of isomorphic reactive code. It’s the beauty of Signals and Effects, being able to group logic together like this. Yes, I can split my code into pieces for frontend and backend, to workaround the Meteor requirement of promises on backend, but I’m just saying that it’s less ideal to have to do so.
That’s the thing: yeah, there are workarounds like I just described in the previous reply to Jam, such as splitting isomorphic code into server vs client pieces rather isomorphic pieces, because it is now necessary. I’m only saying that it wasn’t required before, and that it was actually a really nice thing to be able to write isomorphic also-reactive code.
That’s ok, but note that this isn’t really related to pubsub as I’ve described for Jam. Pubsub served the same purpose before the promise-based API as it does now, nothing has changed with pubsub in that regard. This is only describing how Meteor.methods work, and the fact that they are no longer isomorphically reactive (without having to use difficult workarounds like Tracker.withComputation for every reactive site, which also opens up a potential bag of other async race condition issues that are maybe subtle and more difficult to debug).
Yes, semantically that’s correct. After I set up the subscription, I use the Collection API, e.g. .find, for reading the data.
Most of my introduction has come from Svelte 5. Seems like it and Solid share a lot of the same things under the hood when it comes to signals and effects. Svelte’s syntax just makes them a little nicer to work with imo. With that said, I certainly haven’t gone too deep on this stuff.
Yep, I think this is mostly it. I’m wondering what its benefits are compared to the way I’m used to. So far I’m not seeing them but I’m open to them existing.
Yes indeed! Solid.js popularized “signals and effects”, and various frameworks have followed suit: Vue already had them, albeit not in the spotlight, but now they are and this became apparent with Vue’s setup scripts, while Preact, Lit, Angular, and Svelte have all since introduced them also.
Svelte 5’s are code named “runes”. Prior to Svelte 5, Svelte had something similar with its let and $: variable declarations and $: reactive blocks, but they were much more limited (as limited as React Hooks). Svelte 5’s runes are still not quite like Meteor’s or Solid’s, which are runtime APIs. Signals and effects in Meteor, Solid, Preact Signals, Angular Signals, Vue Reactivity, are simply plain runtime APIs that do not require a build tool, we can simply import them and use them in any plain JS file without needing to compile anything unlike with Svelte Runes.
As an example, see here that there is an error when trying to use Svelte runes in a plain JS file:
And here’s an example showing how to import Solid.js and use it in a plain JS file with absolutely no build tool needed, and without being required to define components (unlike React Hooks):
The conversation got a little bit off topic though haha. This is good stuff to know though, so I’m happy to share it.
Have you been nesting effects yet (or in Meteor terms, nesting Tracker.autoruns)? That’s when their usefulness really starts to shine.