meteor-methods

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Meteor methods

Meteor 方法

Methods are Meteor's primitive for server-side mutation called from the client. In Meteor 3 they are async on the server. Latency compensation still works via client-side stubs.
方法是Meteor中用于从客户端调用服务端数据变更的基础组件。在Meteor 3中,服务端的方法是异步的。延迟补偿仍可通过客户端存根实现。

Decision flow

决策流程

  1. Is this code mutating server data? Use a method.
  2. Does the client need to read the result before the server replies? Write a client stub with the same name; it mutates the local Minimongo collection and the change is reverted if the server disagrees.
  3. Does the method accept untrusted input? Validate every argument with
    check()
    . Otherwise the agent should refuse to write the method.
  4. Is the method rate-sensitive? Add a
    DDPRateLimiter.addRule
    .
  1. 代码是否会修改服务端数据?使用方法。
  2. 客户端是否需要在服务端回复前读取结果?编写一个同名的客户端存根;它会修改本地Minimongo集合,若服务端返回不同结果,该变更会被回滚。
  3. 方法是否接受不可信输入?使用
    check()
    校验每个参数。否则应拒绝编写该方法。
  4. 方法是否对速率敏感?添加
    DDPRateLimiter.addRule
    。

Scaffold

脚手架代码

javascript
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";

Meteor.methods({
  async addItem(payload) {
    check(payload, {
      title: String,
      qty: Match.Integer,
    });
    if (!this.userId) {
      throw new Meteor.Error("not-authorized");
    }
    const _id = await Items.insertAsync({
      ...payload,
      ownerId: this.userId,
      createdAt: new Date(),
    });
    return _id;
  },
});
javascript
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";

Meteor.methods({
  async addItem(payload) {
    check(payload, {
      title: String,
      qty: Match.Integer,
    });
    if (!this.userId) {
      throw new Meteor.Error("not-authorized");
    }
    const _id = await Items.insertAsync({
      ...payload,
      ownerId: this.userId,
      createdAt: new Date(),
    });
    return _id;
  },
});

Calling from the client

从客户端调用

javascript
try {
  const id = await Meteor.callAsync("addItem", { title: "Hi", qty: 1 });
  setLocalId(id);
} catch (err) {
  if (err && typeof err === "object" && "error" in err) {
    console.error(err.error, err.reason, err.details);
  } else {
    console.error("local or transport failure", err);
  }
}
javascript
try {
  const id = await Meteor.callAsync("addItem", { title: "Hi", qty: 1 });
  setLocalId(id);
} catch (err) {
  if (err && typeof err === "object" && "error" in err) {
    console.error(err.error, err.reason, err.details);
  } else {
    console.error("本地或传输失败", err);
  }
}

Optimistic UI

乐观UI

Define the same method on the client. The client stub runs immediately against the local Minimongo; the server's authoritative result reverts any divergence.
javascript
// client/methods.js
import { Meteor } from "meteor/meteor";
import { Random } from "meteor/random";
Meteor.methods({
  addItem(payload) {
    Items.insert({
      ...payload,
      _id: Random.id(),
      ownerId: Meteor.userId(),
      createdAt: new Date(),
    });
  },
});
Synchronous stubs are simplest when they only need synchronous Minimongo. Async stubs are also supported by
callAsync
and are useful when the same method definition runs on client and server with
*Async
collection calls. An async stub may await microtask-based local work, but it must not wait on
fetch
, timers, IndexedDB, workers, or other browser macrotask APIs. Run external I/O outside the stub.
在客户端定义同名方法。客户端存根会立即针对本地Minimongo执行;服务端的权威结果会回滚所有不一致的变更。
javascript
// client/methods.js
import { Meteor } from "meteor/meteor";
import { Random } from "meteor/random";
Meteor.methods({
  addItem(payload) {
    Items.insert({
      ...payload,
      _id: Random.id(),
      ownerId: Meteor.userId(),
      createdAt: new Date(),
    });
  },
});
当存根仅需同步操作Minimongo时,同步存根是最简单的选择。
callAsync
也支持异步存根,当同一方法定义同时运行在客户端和服务端且使用
*Async
集合调用时,异步存根会很有用。异步存根可以等待基于微任务的本地操作,但不能等待
fetch
、定时器、IndexedDB、Worker或其他浏览器宏任务API。应在存根外部执行外部I/O操作。

Rate limiting

速率限制

javascript
import { DDPRateLimiter } from "meteor/ddp-rate-limiter";

DDPRateLimiter.addRule(
  {
    type: "method",
    name: "addItem",
    userId: (userId) => Boolean(userId),
  },
  5,        // operations
  10000,    // per 10s
);
Meteor 3.5+ permits async matcher functions for database-backed decisions. On Meteor 3.0 through 3.4, matchers must stay synchronous; use a fixed rule, precomputed synchronous state, or upgrade instead of awaiting Mongo in a matcher.
Meteor awaits async matchers sequentially on the incoming connection's message queue. Project only required fields and keep the lookup fast. A rejected matcher Promise errors the invocation; test that path explicitly.
See
references/rate-limiting.md
for the rule-object schema and per-connection vs per-user keys.
javascript
import { DDPRateLimiter } from "meteor/ddp-rate-limiter";

DDPRateLimiter.addRule(
  {
    type: "method",
    name: "addItem",
    userId: (userId) => Boolean(userId),
  },
  5,        // 操作次数
  10000,    // 每10秒
);
Meteor 3.5+允许使用异步匹配器函数来实现基于数据库的决策。在Meteor 3.0至3.4版本中,匹配器必须保持同步;请使用固定规则、预计算的同步状态,或者升级版本,不要在匹配器中等待Mongo操作。
Meteor会在传入连接的消息队列中按顺序等待异步匹配器完成。仅投影所需字段并保持查询速度。匹配器Promise拒绝会导致调用出错;请显式测试该路径。
有关规则对象的 schema 以及按连接 vs 按用户的限制键,请查看
references/rate-limiting.md
。

Error handling

错误处理

Throw
Meteor.Error(code, reason, details?)
for an intentional client-visible failure. A plain
Error
is logged on the server and sanitized for the client as
Meteor.Error(500, "Internal server error")
; its original message and stack are not exposed.
Meteor.Error
carries its code, reason, and details. Do not assume every
callAsync
rejection has that shape: callback misuse, transport failures, and local stub exceptions can produce native or arbitrary errors. Narrow the caught value before reading Meteor-specific fields.
对于需要让客户端可见的预期失败,抛出
Meteor.Error(code, reason, details?)
。普通的
Error
会在服务端记录日志,并在客户端被处理为
Meteor.Error(500, "Internal server error")
;其原始消息和堆栈不会暴露给客户端。
Meteor.Error
会携带错误码、原因和详细信息。不要假设每个
callAsync
的拒绝结果都是这种格式:回调误用、传输失败和本地存根异常都可能产生原生错误或任意错误。在读取Meteor特定字段前,请先缩小捕获值的范围。

Anti-patterns

反模式

  • Methods without
    check()
    on every argument. Reject the method in code review.
  • Reusing a method for both authenticated and unauthenticated calls. Split into two methods.
  • Awaiting browser macrotask APIs such as
    fetch
    or timers inside a client stub. Async stubs are valid, but those APIs let unrelated code run before the optimistic simulation finishes.
  • Calling
    Meteor.user()
    inside an async method body. Use
    this.userId
    .
  • 未对每个参数使用
    check()
    的方法。在代码审查中拒绝此类方法。
  • 同一个方法同时用于已认证和未认证调用。拆分为两个方法。
  • 在客户端存根中等待浏览器宏任务API,如
    fetch
    或定时器。异步存根本身是合法的,但这些API会让无关代码在乐观模拟完成前运行。
  • 在异步方法体内调用
    Meteor.user()
    。请使用
    this.userId
    。

See also

另请参阅

  • references/check-and-validate.md
  • references/rate-limiting.md
  • references/eval-cases.md
  • references/check-and-validate.md
  • references/rate-limiting.md
  • references/eval-cases.md