diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index 9b0596bfa1..a6c5d25c0d 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -15,9 +15,8 @@ function withoutInvocation(f) { return function () { CurrentInvocation.withValue(null, f); }; - } else { - return f; } + return f; } function bindAndCatch(context, f) { @@ -56,7 +55,7 @@ Meteor.setInterval = function (f, duration) { * @locus Anywhere * @param {Object} id The handle returned by `Meteor.setInterval` */ -Meteor.clearInterval = function(x) { +Meteor.clearInterval = function (x) { return clearInterval(x); }; @@ -66,7 +65,7 @@ Meteor.clearInterval = function(x) { * @locus Anywhere * @param {Object} id The handle returned by `Meteor.setTimeout` */ -Meteor.clearTimeout = function(x) { +Meteor.clearTimeout = function (x) { return clearTimeout(x); }; @@ -84,3 +83,54 @@ Meteor.clearTimeout = function(x) { Meteor.defer = function (f) { Meteor._setImmediate(bindAndCatch("defer callback", f)); }; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background based on environment (similar to Meteor.isDevelopment ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + * @param {Array} options.on Condition to determine whether to defer the function, you can pass an array of environments ['development', 'production', 'test'] + */ +Meteor.deferrable = function (f, options) { + var on = (options && options.on) || []; + + // throw if on is not an array + if (!Array.isArray(on)) { + throw new Error("options.on must be an array"); + } + + var env = Meteor.isDevelopment + ? "development" + : Meteor.isProduction + ? "production" + : "test"; + + if (on.includes(env)) { + return Meteor.defer(f); + } + + return f(); +}; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background in development (similar to Meteor.isDevelopment ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + */ +Meteor.deferDev = function (f) { + return Meteor.deferrable(f, { on: ["development", "test"] }); +}; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background in production (similar to Meteor.isProduction ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + */ +Meteor.deferProd = function (f) { + return Meteor.deferrable(f, { on: ["production"] }); +}; diff --git a/packages/meteor/timers_tests.js b/packages/meteor/timers_tests.js index 246f7e7b39..1db203dd00 100644 --- a/packages/meteor/timers_tests.js +++ b/packages/meteor/timers_tests.js @@ -1,21 +1,77 @@ -Tinytest.addAsync('timers - defer', function (test, onComplete) { - var x = 'a'; +Tinytest.addAsync("timers - defer", function (test, onComplete) { + let x = "a"; Meteor.defer(function () { - test.equal(x, 'b'); + test.equal(x, "b"); onComplete(); }); - x = 'b'; + x = "b"; }); -Tinytest.addAsync('timers - nested defer', function (test, onComplete) { - var x = 'a'; +Tinytest.addAsync("timers - nested defer", function (test, onComplete) { + let x = "a"; Meteor.defer(function () { - test.equal(x, 'b'); + test.equal(x, "b"); Meteor.defer(function () { - test.equal(x, 'c'); + test.equal(x, "c"); onComplete(); }); - x = 'c'; + x = "c"; }); - x = 'b'; + x = "b"; }); + +Tinytest.addAsync("timers - deferrable", function (test, onComplete) { + let x = "a"; + Meteor.deferrable( + function () { + test.equal(x, "b"); + onComplete(); + }, + { on: ["development", "production", "test"] } + ); + x = "b"; +}); + +Tinytest.addAsync( + "timers - deferrable not in current env", + function (test, onComplete) { + let x = "a"; + Meteor.deferrable( + function () { + x = "b"; + }, + { on: [] } + ); + test.equal(x, "b"); + onComplete(); + } +); + +Tinytest.addAsync( + "timers - deferrable works with async functions", + function (test, onComplete) { + let x = Meteor.deferrable( + function () { + return "start value"; + }, + { on: [] } + ); + test.equal(x, "start value"); + + Meteor.deferrable( + function () { + test.equal(x, "value"); + onComplete(); + }, + { on: ["development", "production", "test"] } + ); + + Meteor.deferrable( + async function () { + return "value"; + }, + { on: [] } + ).then((value) => (x = value)); + + } +); diff --git a/v3-docs/docs/api/meteor.md b/v3-docs/docs/api/meteor.md index 5f3899014a..5291376348 100644 --- a/v3-docs/docs/api/meteor.md +++ b/v3-docs/docs/api/meteor.md @@ -54,6 +54,86 @@ Meteor.startup(() => { + + +This helper function allows you to defer the execution of a function based on the environment. + +::: code-group + +```js [with-deferrable.js] +import { Meteor } from "meteor/meteor"; + +Meteor.startup(async () => { + await Meteor.deferrable(connectToExternalDB, { + on: ["development"], + }); +}); +``` + +```js [without-deferrable.js] +import { Meteor } from "meteor/meteor"; + +Meteor.startup(async () => { + if (Meteor.isDevelopment) { + Meteor.defer(connectToExternalDB); + } else { + await connectToExternalDB(); + } +}); +``` + +::: + +Using this pattern can get some performance gains on the defined environments as sometimes we do not need to wait for this function, +this can increase the speed of startup. + + +This helper function allows you to defer the execution of a function only in development environments. + +::: code-group + +```js [with-deferrable.js] +import { Meteor } from "meteor/meteor"; +Meteor.startup(async () => { + await Meteor.deferDev(connectToExternalDB); +}); +``` + +```js [without-deferrable.js] +import { Meteor } from "meteor/meteor"; +Meteor.startup(async () => { + if (Meteor.isTest || Meteor.isDevelopment) { + Meteor.defer(connectToExternalDB); + } else { + await connectToExternalDB(); + } +}); +``` + + + +This helper function allows you to defer the execution of a function only in production environments. +::: code-group + +```js [with-deferrable.js] +import { Meteor } from "meteor/meteor"; +Meteor.startup(async () => { + await Meteor.deferProd(loadDevTools); +}); +``` + +```js [without-deferrable.js] +import { Meteor } from "meteor/meteor"; + +Meteor.startup(async () => { + if (Meteor.isProduction) { + Meteor.defer(loadDevTools); + } else { + await loadDevTools(); + } +}); +``` + @@ -398,7 +478,6 @@ even if the method's writes are not available yet, you can specify an Use `Meteor.call` only to call methods that do not have a stub, or have a sync stub. If you want to call methods with an async stub, `Meteor.callAsync` can be used with any method. ::: - `Meteor.callAsync` is just like `Meteor.call`, except that it'll return a promise that you need to solve to get the server result. Along with the promise returned by `callAsync`, you can also handle `stubPromise` and `serverPromise` for managing client-side simulation and server response. @@ -409,64 +488,63 @@ The following sections guide you in understanding these promises and how to mana ```javascript try { - await Meteor.callAsync('greetUser', 'John'); - // ๐ŸŸข Server ended with success -} catch(e) { - console.error("Error:", error.reason); // ๐Ÿ”ด Server ended with error + await Meteor.callAsync("greetUser", "John"); + // ๐ŸŸข Server ended with success +} catch (e) { + console.error("Error:", error.reason); // ๐Ÿ”ด Server ended with error } -Greetings.findOne({ name: 'John' }); // ๐Ÿ—‘๏ธ Data is NOT available +Greetings.findOne({ name: "John" }); // ๐Ÿ—‘๏ธ Data is NOT available ``` #### stubPromise ```javascript -await Meteor.callAsync('greetUser', 'John').stubPromise; +await Meteor.callAsync("greetUser", "John").stubPromise; // ๐Ÿ”ต Client simulation -Greetings.findOne({ name: 'John' }); // ๐Ÿงพ Data is available (Optimistic-UI) +Greetings.findOne({ name: "John" }); // ๐Ÿงพ Data is available (Optimistic-UI) ``` #### stubPromise and serverPromise ```javascript -const { stubPromise, serverPromise } = Meteor.callAsync('greetUser', 'John'); +const { stubPromise, serverPromise } = Meteor.callAsync("greetUser", "John"); await stubPromise; // ๐Ÿ”ต Client simulation -Greetings.findOne({ name: 'John' }); // ๐Ÿงพ Data is available (Optimistic-UI) +Greetings.findOne({ name: "John" }); // ๐Ÿงพ Data is available (Optimistic-UI) try { await serverPromise; // ๐ŸŸข Server ended with success -} catch(e) { +} catch (e) { console.error("Error:", error.reason); // ๐Ÿ”ด Server ended with error } -Greetings.findOne({ name: 'John' }); // ๐Ÿ—‘๏ธ Data is NOT available +Greetings.findOne({ name: "John" }); // ๐Ÿ—‘๏ธ Data is NOT available ``` #### Meteor 2.x contrast For those familiar with legacy Meteor 2.x, the handling of client simulation and server response was managed using fibers, as explained in the following section. This comparison illustrates how async inclusion with standard promises has transformed the way Meteor operates in modern versions. -``` javascript -Meteor.call('greetUser', 'John', function(error, result) { +```javascript +Meteor.call("greetUser", "John", function (error, result) { if (error) { console.error("Error:", error.reason); // ๐Ÿ”ด Server ended with error } else { console.log("Result:", result); // ๐ŸŸข Server ended with success } - Greetings.findOne({ name: 'John' }); // ๐Ÿ—‘๏ธ Data is NOT available + Greetings.findOne({ name: "John" }); // ๐Ÿ—‘๏ธ Data is NOT available }); // ๐Ÿ”ต Client simulation -Greetings.findOne({ name: 'John' }); // ๐Ÿงพ Data is available (Optimistic-UI) +Greetings.findOne({ name: "John" }); // ๐Ÿงพ Data is available (Optimistic-UI) ``` - `Meteor.apply` is just like `Meteor.call`, except that the method arguments are @@ -504,8 +582,6 @@ different collections. We hope to lift this restriction in a future release. - - ```js import { Meteor } from "meteor/meteor"; import { check } from "meteor/check";