mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge pull request #14006 from meteor/feature/meteor.deferrable
FEATURE: `Meteor.deferrable`
This commit is contained in:
@@ -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<String>} 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"] });
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
@@ -54,6 +54,86 @@ Meteor.startup(() => {
|
||||
|
||||
<ApiBox name="Meteor.promisify" />
|
||||
<ApiBox name="Meteor.defer" />
|
||||
<ApiBox name="Meteor.deferrable" hasCustomExample />
|
||||
|
||||
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.
|
||||
|
||||
<ApiBox name="Meteor.deferDev" hasCustomExample />
|
||||
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();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<ApiBox name="Meteor.deferProd" hasCustomExample />
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<ApiBox name="Meteor.absoluteUrl" />
|
||||
<ApiBox name="Meteor.settings" />
|
||||
<ApiBox name="Meteor.release" />
|
||||
@@ -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.
|
||||
:::
|
||||
|
||||
|
||||
<ApiBox name="Meteor.callAsync" />
|
||||
|
||||
`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)
|
||||
```
|
||||
|
||||
|
||||
<ApiBox name="Meteor.apply" />
|
||||
|
||||
`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.
|
||||
|
||||
</ApiBox>
|
||||
|
||||
|
||||
|
||||
```js
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { check } from "meteor/check";
|
||||
|
||||
Reference in New Issue
Block a user