Sure:
In the past we could write this:
Meteor.methods({
someMethod() {
console.log('books count:', Books.find({}).count())
console.log('authors count:', Authors.find({}).count())
}
})
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:
Meteor.methods({
async someMethod() {
console.log('books count:', await Books.find({}).countAsync())
console.log('authors count:', await Authors.find({}).countAsync())
}
})
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,
Tracker.autorun(() => {
Meteor.call('someMethod')
})
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:
if (Meteor.isClient) {
Meteor.methods({
someMethodClient() {
console.log('books count:', Books.find({}).count())
console.log('authors count:', Authors.find({}).count())
}
})
} else {
Meteor.methods({
async someMethodServer() {
console.log('books count:', await Books.find({}).countAsync())
console.log('authors count:', await Authors.find({}).countAsync())
}
})
}
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.