waitOn.md
April 22, 2026 · View on GitHub
waitOn hook
waitOn(params, queryParams, ready)
params{Object} - Serialized route parameters,/route/:_id => { _id: 'str' }queryParams{Object} - Query params object,/route/?key=val => { key: 'val' }ready{Function} - Call when computation is ready using Tracker- Return: {Promise|[Promise]|Subscription|[Subscription]|Tracker|[Tracker]}
.waitOn() hook is triggered before .action() hook, allowing to load necessary data before rendering a template.
maxWaitFor (route and router)
- Per route:
FlowRouter.route(path, { maxWaitFor, waitOn, action, … })— max time in milliseconds for:- resolving
waitOnpromises (includingasync waitOnand arrays of promises), and - waiting until every returned subscription-like handle’s
ready()is true (polled every 24ms).
- resolving
- Router default:
FlowRouter.maxWaitFordefaults to120000(same as package exportMAX_WAIT_FOR_MS). Set viaFlowRouter.initialize({ maxWaitFor })or assignFlowRouter.maxWaitFor = …on the singleton. Routes without an explicitmaxWaitForuseFlowRouter.maxWaitForat the timewaitOnruns (so a laterinitialize/ assignment still applies). - If
maxWaitForelapses while promises or subscriptions are still pending,waitOnends and the route still runstriggersEnter/action(timeout is logged). Navigation away abortswaitOnand skipsactionfor the route being left.
Subscriptions
FlowRouter.route('/post/:_id', {
name: 'post',
waitOn(params) {
return [Meteor.subscribe('post', params._id), Meteor.subscribe('suggestedPosts', params._id)];
}
});
Tracker
Use reactive data sources inside waitOn hook. To make waitOn rerun on reactive data changes, wrap it to Tracker.autorun and return Tracker Computation object or an Array of Tracker Computation objects. Note: the third argument of waitOn is ready callback.
FlowRouter.route('/posts', {
name: 'post',
waitOn(params, queryParams, ready) {
return Tracker.autorun(() => {
ready(() => {
return Meteor.subscribe('posts', search.get(), page.get());
});
});
}
});
Array of Trackers
FlowRouter.route('/posts', {
name: 'post',
waitOn(params, queryParams, ready) {
const tracks = [];
tracks.push(Tracker.autorun(() => {
ready(() => {
return Meteor.subscribe('posts', search.get(), page.get());
});
}));
tracks.push(Tracker.autorun(() => {
ready(() => {
return Meteor.subscribe('comments', postId.get());
});
}));
return tracks;
}
});
Promises
FlowRouter.route('/posts', {
name: 'posts',
waitOn() {
return new Promise((resolve, reject) => {
loadPosts((err) => {
(err) ? reject() : resolve();
});
});
}
});
Array of Promises
FlowRouter.route('/posts', {
name: 'posts',
waitOn() {
return [new Promise({/*..*/}), new Promise({/*..*/}), new Promise({/*..*/})];
}
});
Meteor method via Promise
Deprecated, since v3.12.0 Meteor.callAsync can get called inside async data() hook to retrieve data from a method
FlowRouter.route('/posts', {
name: 'posts',
conf: {
posts: false
},
action(params, queryParams, data) {
this.render('layout', 'posts', data);
},
waitOn() {
return new Promise((resolve, reject) => {
Meteor.call('posts.get', (error, posts) => {
if (error) {
reject();
} else {
// Use `conf` as shared object to
// pass it from `data()` hook to
// `action()` hook`
this.conf.posts = posts;
resolve();
}
});
});
},
data() {
return this.conf.posts;
}
});
Dynamic import
FlowRouter.route('/posts', {
name: 'posts',
waitOn() {
return import('/imports/client/posts.js');
}
});
Array of dynamic import(s)
FlowRouter.route('/posts', {
name: 'posts',
waitOn() {
return [
import('/imports/client/posts.js'),
import('/imports/client/sidebar.js'),
import('/imports/client/footer.js')
];
}
});
Dynamic import and Subscription
FlowRouter.route('/posts', {
name: 'posts',
waitOn() {
return [import('/imports/client/posts.js'), Meteor.subscribe('Posts')];
}
});
async support
FlowRouter.route('/posts', {
name: 'posts',
async waitOn() {
await import('/imports/client/posts.js');
return Meteor.subscribe('Posts');
}
});