Methods

通过阅读本章,你将会了解到:

  1. 什么是流星中的Methods以及它们是怎么样工作的。
  2. 定义和调用Methods的最佳实践。
  3. 如何在Mehtods里抛出和处理错误。
  4. 如何在表单里调用一个Method。

什么是一个Method

Methods是流星的远程过程调用(RPC)系统,用来保存用户输入事件和来自客户端而来的数据。如果你熟悉REST APIs或HTTP,你会把它们想象成为你服务器的POST请求,但是通过很多的优秀的功能为了构建一个现代化的web应用程序而优化的。稍后在本章中,我们将细化这些从Methods中特有的、但却不能从HTTP终端中获得的优势。

一个Method的核心就是一个服务器API终端;你能够在服务器端定义一个Method并且在客户端定义它的相应的部分,接着通过一些数据调用它,写入到数据库,并且在一个回调中获得返回值。流星Methods也是紧紧地和发布/订阅绑定并且流星的数据装载系统允许乐观的用户界面 — 在客户端模拟服务器端动作让应用给人感觉上比较快的能力。

我们使用大写的M开头表示流星的Mehtods以区别于JavaScript的类方法。

定义和调用Methods

基本Method

在一个复杂的程序中,你可能要点额外的功能才能让Methods更具有威力和易于测试。首先,我们打算过一下如何通过使用流星的核心API来定义一个Method,在稍后的部分我们将介绍如何使用一个有用的包装器包(由我们创建的用来激活更有威力的Mehtod工作流)。

Defining定义

这里是如何使用内置的Meteor.methods API来定义一个Method。注意Mehtods应该总是被定义在通常的代码中,用来被装载在客户端和服务器以激活乐观的UI。如果你有些秘密的代码,请查看安全一章来获知如何对客户端进行隐藏。

本例使用aldeed:simple-schema包,也在其它几篇文章里被推荐,用来验证Method参数。

Meteor.methods({
  'Todos.methods.updateText'({ todoId, newText }) {
    new SimpleSchema({
      todoId: { type: String },
      newText: { type: String }
    }).validate({ todoId, newText });

    const todo = Todos.findOne(todoId);

    if (!todo.editableBy(this.userId)) {
      throw new Meteor.Error('Todos.methods.updateText.unauthorized',
        'Cannot edit todos in a private list that is not yours');
    }

    Todos.update(todoId, {
      $set: { text: newText }
    });
  }
});

调用

This Method is callable from the client and server using Meteor.call. Note that you should only use a Method in the case where some code needs to be callable from the client; if you just want to modularize code that is only going to be called from the server, use a regular JavaScript function, not a Method.这个 Method 可以从客户端和服务器端进行调用,通过使用 Meteor.call。注意你应该在有需要调用从客户端发起时,仅仅使用Method;如果你只是需要模块化代码,而代码是从服务器端调用,请使用正常的JavaScript函数,而不是Method。

以下是从客户端发起对于一个方法的调用:

Meteor.call('Todos.methods.updateText', {
  todoId: '12345',
  newText: 'This is a todo item.'
}, (err, res) => {
  if (err) {
    alert(err);
  } else {
    // 成功!
  }
});

如果 Method 抛出一个错误,你会在回调函数的第一个参数中获得它。如果Method成功,你在第二个参数中获得其结果并且第一个参数err的值是undefined。要获得关于错误的更多信息,参见以下关于错误处理的章节。

高级 Method 的boilerplate

流星 Methods 有些功能并不是那么显而易见,但每个复杂的应用或多或少在某些方面都需要这些功能。这些功能是多年来以一种 向前-兼容 的方式累积起来的,所以解锁Methods所有能力需要大量的boilerplate。本文中我们会首先向你展示对于每项功能的代码,在下一节会讨论一个 Method 包装器 — 这是由我们开发的,为了让事情变得容易些。

一个理想的 Method 会具有以下的一些功能:

  1. 自己验证的代码,不需要通过运行 Mehtod的body。
  2. 容易覆写该方法用来测试。
  3. 容易通过一个自定义的用户ID来调用方法,尤其是在测试中(就像Discover Meteor two-tiered methods pattern推荐的那样)。
  4. 通过JS模块来引用 Method 比使用一个魔法字符串更好。
  5. 通过 Method模拟 的返回值来获得插入的文档的ID。
  6. 如果客户端验证失败,避免调用服务器端的 Method,以免浪费服务器端资源。

定义

// 定义一个名称空间,用于Methods相关的 Todos 集合
// 允许通过替换实现(2)来做测试
Todos.methods = {};

Todos.methods.updateText = {
  name: 'Todos.methods.updateText',

  // 提出验证函数以便可以独立地运行着个函数(1)
  validate(args) {
    new SimpleSchema({
      todoId: { type: String },
      newText: { type: String }
    }).validate(args)
  },

  // 提出Method body以便于它可以被单独调用(3)
  run({ todoId, newText }) {
    const todo = Todos.findOne(todoId);

    if (!todo.editableBy(this.userId)) {
      throw new Meteor.Error('Todos.methods.updateText.unauthorized',
        'Cannot edit todos in a private list that is not yours');
    }

    Todos.update(todoId, {
      $set: { text: newText }
    });
  },

  // 通过引用的 JS对象 调用 Method(4)
  // 而且这可以让我们在该Method实现中指定 Meteor.apply 选项,而不是需要在调用者在调用时进行指定。
  call(args, callback) {
    const options = {
      returnStubValue: true,     // (5)
      throwStubExceptions: true  // (6)
    }

    Meteor.apply(this.name, [args], options, callback);
  }
};

// 实际通过流星的DDP系统实际注册该方法
Meteor.methods({
  [Todos.methods.updateText.name]: function (args) {
    Todos.methods.updateText.validate.call(this, args);
    Todos.methods.updateText.run.call(this, args);
  }
})

调用

Now calling the Method is as simple as calling a JavaScript function:现在就可以像调用一个JavaScript函数一样简单地去调用Method。

// 调用该Method
Todos.methods.updateText.call({
  todoId: '12345',
  newText: 'This is a todo item.'
}, (err, res) => {
  if (err) {
    alert(err);
  } else {
    // success!
  }
});

// 仅调用验证
Todos.methods.updateText.validate({ wrong: 'args'});

// 在测试中使用自定义userId进行调用
Todos.methods.updateText.run.call({ userId: 'abcd' }, {
  todoId: '12345',
  newText: 'This is a todo item.'
});

As you can see, this approach to calling Methods results in a better development workflow - you can more easily deal with the different parts of the Method separately and test your code more easily without having to deal with Meteor internals. But this approach requires you to write a lot of boilerplate on the Method definition side.可以看到用这种方法调用Methods会形成一个更好的开发工作流 — 可以更容易地处理Method的不同的部分,并且可以更容易地测试你的代码而不需要应对流星内部(方法)。但这种方法需要在Method定义端去写很多的boilerplate。

使用 mdg:validated-method 的高级Methods

为了缓解在正确的Method定义中的boilerplate代码工作量,我们发布了一个包装器包叫做mdg:validated-method,它为你做了很多的工作。下面是使用了这个包的 Method定义,针对上面相同的代码:

Todos.methods.updateText = new ValidatedMethod({
  name: 'Todos.methods.updateText',
  validate: new SimpleSchema({
    todoId: { type: String },
    newText: { type: String }
  }).validator(),
  run({ todoId, newText }) {
    const todo = Todos.findOne(todoId);

    if (!todo.editableBy(this.userId)) {
      throw new Meteor.Error('Todos.methods.updateText.unauthorized',
        'Cannot edit todos in a private list that is not yours');
    }

    Todos.update(todoId, {
      $set: { text: newText }
    });
  }
});

调用部分是和上面一致的,但Method定义却简化了很多。我们相信这种风格的Method让你能够更清晰地看到其重要的部分 — Method的名字通过连线、期待的参数的值的格式、以及JavaScript命名空间(该Method能够被引用)。

错误处理

在常规的JavaScript方法里,你可以通过抛出 Error 对象来指示错误。从流星的 Methods 抛出的错误几乎也是这样,但有点复杂的部分是,在一些情况下,错误对象会通过websocket发送到客户端。

从一个 Method 中抛出错误

流星引入两个新的JavaScript错误类型: Meteor.ErrorValidationError。这两种和常规的JavaScript Error 类型应该在不同的情况下进行使用:

常规 Error 对于服务器内部的错误

当有个错误不需要上报到客户端,而仅对于服务器内部而言,那请抛出一个常规的JavaScript错误对象。这对于客户端是完全不透明的。

Meteor.Error对于通常的运行时错误

当服务器由于某种已知的条件不能完成用户期望的动作时候,你应该抛出一个描述性的 Metehor.Error 对象给客户端。在 Todos 示例程序中,我们使用它们来报告情况 — 当前用户没有授权去完成某个特定的动作,或该动作在应用中不被允许执行 — 例如,不能删除最后一个公共的list。

Meteor.Error 具有三个参数:errorreason,和details

  1. error应该是短的,唯一的,机器可读的错误代码字符串,用来在客户端截取来理解发生了什么。最好是使用Method的名字作为前缀,这样可以容易地进行国际化。例如:'Todos.methods.updateText.unauthorized’
  2. reason应该是一个简短的错误描述,是为开发者服务的。这应该给予你的同事足够的信息来debug这个错误。reason参数不应该直接打印到终端用户面前,因为现在在服务器端发送错误信息之前,你需要处理国际化,并且该用户界面的开发者必须去考虑 Method的实现 — 究竟怎么样的信息应该被显示在UI上。
  3. details 是可选的,而且能够被用来在额外的数据里帮助客户端理解哪里出现了错误。尤其,它可以和 error 字段一起使用向终端用户打印更多有用的错误信息。

使用ValidationError进行参数验证的错误

当一个Method的参数类型发生错误而导致调用失败,应该抛出一个 ValidationError。就像 Meteor.Error 一样,但却是一个自定义的构造器用来强制一个标准错误格式,这种格式能够被不同的表单和验证库进行读取。尤其是,如果你从一个表单中调用该方法,抛出一个 ValidationError 会很容易显示良好的错误信息在表单中相应的字段的旁边。

When you use mdg:validated-method with aldeed:simple-schema as demonstrated above, this type of error is thrown for you.当你通过 aldeed:simple-schema 来使用 mdg:validated-method 的时候,如上所示,这种类型错误会被抛出来。

可以在 mdg:validation-error 的文档 中阅读更多的错误格式相关信息。

处理错误

When you call a Method, any errors thrown by it will be returned in the callback. At this point, you should identify which error type it is and display the appropriate message to the user. In this case, it is unlikely that the Method will throw a ValidationError or an internal server error, so we will only handle the unauthorized error:当你调用一个 Mehtod 时,任意由它抛出的错误会通过它的回调进行返回。从这点上而言,你应该甄别它的错误类型并且向用户显示适当的信息。这种情况下该Method不应该像抛出一个 ValidationError 或 一个内部服务器错误那样,我们应该处理非授权的错误:

// 调用该 Method
Todos.methods.updateText.call({
  todoId: '12345',
  newText: 'This is a todo item.'
}, (err, res) => {
  if (err) {
    if (err.error === 'Todos.methods.updateText.unauthorized') {
      // 显示一个警告或许并不是你在一个真正的应用中会做的事情;你应该用些比较好的UI
      // 来显示这个错误,而且可能会使用一个i18n的库从错误代码中产生信息。
      alert('You aren\'t allowed to edit this todo item');
    } else {
      // 意料之外的错误,在一些UI中进行处理
    }
  } else {
    // 成功!
  }
});

下面章节我们会讨论在表单上如何处理 ValidationError

Method模拟中的错误

当一个Method被调用时,它通常会运行两次 — 一次是在客户端为乐观的UI来模拟结果,再一次是在服务器端做对于数据库实际的改变。这意味着如果你的Method抛出一个错误,它会在客户端 服务器端失败。因此,ValidateMethod 开启了未文档的流星选项用来避免调用服务器端的实现,如果模拟抛出一个错误的话。

这种行为可以很好的节约服务器资源,万一Method失败了。要确保服务器上方法会成功(例如,如果你不需要在客户端加载一些数据,该Method需要去做相应的模拟。)而模拟不会抛出一个错误是相当重要的,在这种情况下,你需要包装服务器端的相应的逻辑到某个代码块用来检测一个方法的模拟:

if (!this.isSimulation) {
  // Logic that depends on server environment here
}

从一个表单调用一个Method

通过 ValidationError 的便利主要达成的是Methods和表单之间简单集成的调用。总的来说,你的应用就像有一个一对一的UI中的表单到Methods之间的映射一样。首先,让我们为我们的业务逻辑定义个Method:

// 这个Method编码了表单验证的需求。通过在Method中定义它们,我们可以在同一个地方运行
// 客户端和服务器端的验证。
Invoices.methods.insert = new ValidatedMethod({
  name: 'Invoices.methods.insert',
  validate: new SimpleSchema({
    email: { type: String, regEx: SimpleSchema.RegEx.Email },
    description: { type: String, min: 5 },
    amount: { type: String, regEx: /^\d*\.(\d\d)?$/ }
  }).validator(),
  run(newInvoice) {
    // 在这里,我们应该确保 newInvoice 参数是被验证通过的。

    if (!this.userId) {
      throw new Meteor.Error('Invoices.methods.insert.not-logged-in',
        'Must be logged in to create an invoice.');
    }

    Invoices.insert(newInvoice)
  }
});

让我们来定义个简单的HTML表单:

<template name="Invoices_newInvoice">
  <form class="Invoices_newInvoice">
    <label for="email">Recipient email</label>
    <input type="email" name="email" />
    {{#each error in errors "email"}}
      <div class="form-error">{{error}}</div>
    {{/each}}

    <label for="description">Item description</label>
    <input type="text" name="description" />
    {{#each error in errors "description"}}
      <div class="form-error">{{error}}</div>
    {{/each}}

    <label for="amount">Amount owed</label>
    <input type="text" name="amount" />
    {{#each error in errors "amount"}}
      <div class="form-error">{{error}}</div>
    {{/each}}
  </form>
</template>

现在,让我们写点JavaScript来处理好表单:

Template.Invoices_newInvoice.onCreated(function() {
  this.errors = new ReactiveDict();
});

Template.Invoices_newInvoice.helpers({
  errors(fieldName) {
    return this.errors.get(fieldName);
  }
});

Template.Invoices_newInvoice.events({
  'submit .Invoices_newInvoice'(event) {
    const instance = Template.instance();

    const data = {
      email: event.target.email.value,
      description: event.target.description.value,
      amount: event.target.amount.value
    };

    Invoices.methods.insert.call(data, (err, res) => {
      if (err) {
        if (err.error === 'validation-error') {
          // 初始化错误对象
          const errors = {
            email: [],
            description: [],
            amount: []
          };

          // 从Method遍历验证错误进而返回
          err.details.forEach((fieldError) => {
            // XXX i18n
            errors[fieldError.name].push(fieldError.type);
          });

          // 更新ReactiveDict,错误会显示在UI上
          instance.errors.set(errors);
        }
      }
    });
  }
});

As you can see, there is a fair amount of boilerplate to handle errors nicely in a form, but most of it can be easily abstracted by an off-the-shelf form framework or a simple application-specific wrapper of your own design. The final released version of the Meteor guide will have a new article about forms.如你看到的那样,有相当数量的boilerplate要在一个表单中进行优雅地错误处理,其中的多数是可以通过现成的表单框架或者一个简单的、应用程序-特定的包装器(由你自己设计的)很容易地得到抽象。最终释放的流星指南的版本会有一篇关于表单的新文章。

使用Methods装载数据

由于Methods可以和通用目的的RPCs一起工作,它们也能被用来替代 发布 获取数据。和通过发布装载数据比较,这种方式有些优势也有些不好的地方,而且最终我们还是推荐使用 发布 来装载数据。

Methods会在获取一个复杂的服务器端计算结果、并且不需要当服务器端数据发生变化时进行相应的更新的时候很有用处。通过Methods获取数据最大的不利之处是数据不会自动地装载到Minimongo,流星的客户端的数据缓存,中去,所以你需要手动地管理数据的生命周期。

使用本地集合来存储盒显示从一个Mehtod获取的数据

集合是一个在客户端存储数据非常方便的方式。如果你通过一些非订阅的方式获取数据,你可以将数据放在一个集合里进行手动管理。让我们来看一个例子,我们需要用一个复杂的算法来计算一组玩家的一系列游戏的平均分。我们不想使用一个发布来装载这样的数据是因为我们要精确控制其何时运行,并且不想要这些数据被自动地进行缓存。

首先,你需要创建一个 本地集合 — 这是一个仅存在客户端的集合并且不和服务器上的数据库集合绑定。阅读更多相关信息在 集合一章

// 在客户端的代码里,通过传递 `null` 作为其参数来声明一个本地集合
ScoreAverages = new Mongo.Collection(null);

现在,如果你通过一个Method获取数据,你可以放在这个集合里:

function updateAverages() {
  // 清理结果缓存
  ScoreAverages.remove({});

  // 调用一个Method,而这个Method执行一个昂贵的计算
  Games.methods.calculateAverages.call((err, res) => {
    res.forEach((item) => {
      ScoreAverages.insert(item);
    });
  });
}

我们现在可以在一个UI组件中使用从本地集合ScoreAverages的数据,就像同样使用一个常规的MongoDB集合那样。与其让它自动更新,不如我们在每次需要新的结果时进行调用。

高级概念

一方面,你可以通过一下的流星介绍性的教程很容易地在某个简单应用中使用Methods,它的重要性在于确实地去理解它们是如何工作的,以至于我们可以在一个生产环境的应用中有效地使用它们。使用像流星(已经为你在底层做了很多的工作)这样的框架其中的一个缺点就是,你并不总是知道(底层)到底发生了什么,所以学习一些这方面的核心概念是一个好主意。

Method 调用生命周期

Here's exactly what happens, in order, when a Method is called:以下是,当一个Method被调起的时候,该Method的内部按照先后顺序究竟发生了什么:

1. Method 在客户端进行模拟运行

如果我们在客户端和服务器端都定义了这个Method的代码(所有的Methods都应该这样),一个Method当其被调用时会在客户端进行模拟运行。

(这时)客户端会进入一种特殊的模式,它会跟踪所有导致客户端集合变化的变更,因此可以回滚到之前的状态。当这步完成后,应用的用户可以看到他们的UI立刻根据客户端的数据库内容获得更新,但服务器还未收到任何的数据。

如果有一个异常从Method中被抛出来,那么默认地流星会忽略它并且继续第(2)步。如果你使用 ValidatedMethod 或者向Meteor.apply传递了一个特殊的throwStubExceptions选项,那么异常会从模拟中抛出并且阻止服务器端的Method进行执行。

Method模拟的返回值会被抛弃,除非当调用该Method时候传递了 returnStubValue 的选项,这种情况下它会被返回到该Method的调用者。验证过的Method会默认转递该选项。

2. 一个 method DDP 的消息会被发送到服务器

流星客户端会构建一个DDP消息来发送到服务器。这包括Method名字,参数,和一个自动产生的MethodID用来表示这个特定的Method调用。

3. Method 运行在服务器端

当服务器接受消息时,它会在服务器端再次执行该Method的代码。客户端的版本是一个模拟,可能稍后会被回滚,但这个时刻这还是用来向实际的数据库里写入的真正版本。在服务器端运行真正的Method逻辑是至关重要的,因为服务器端是一个受信任的环境,在那里我们可以确信安全性-敏感的代码会如我们预期的那样进行运行。

4. 将返回值发送回客户端

Once the Method has finished running on the server, it sends a result message to the client with the Method ID generated in step 2, and the return value itself. The client stores this for later use, but doesn't call the Method callback yet. If you pass the onResultReceived option to Meteor.apply, that callback is fired.一旦Method已经在服务器端完成了运行,它会发送一个result消息(伴随一个由第2步产生的Method ID,并且由它自身返回值)到客户端。客户端存储这些值为了稍后使用,但这时, 还没调用Method 回调。如果你传递 onResultReceived 选项到 Meteor.apply,该回调才被触发。

5. 任意 DDP 的发布 会通过该Method被更新后受到影响

If we have any publications on the page that have been affected by the database writes from this Method, the server sends the appropriate updates to the client. Note that the client data system doesn't reveal these updates to the app UI until the next step.如果我们在页面上有任何的发布,由于通过Method向数据库中写入,而受到影响,服务器会发送相应的更新到客户端。注意,这时客户端的数据系统不会揭示这些更新到应用的UI,直到下一步。

6. updated信息发送到客户端,数据被替换成服务器端的结果,Method回调会引发。

相关数据更新已经被发送到正确的客户端之后,服务器端会回发在Method生命周期中最后一条消息 — DDP updated 消息具有相关的Method ID。客户端回滚任意对客户端造成的改变,这些改变时在Method模拟中发生的(第1步),并且替换为通过从第5步服务器端发送回来的实际数据改变。

Lastly, the callback passed to Meteor.call actually fires with the return value from step 4. It's important that the callback waits until the client is up to date, so that your Method callback can assume that the client state reflects any changes done inside the Method.

错误情况

在上面列的情况中,我们没有覆盖到当Method在服务器端执行时发生错误的情况。在这种情况下是没有返回值的,并且客户端会获得一个错误。Method的回调会立刻调起,通过返回的错误作为第一个参数。请在下面阅读更多关于错误处理的信息。

Method优于REST的地方

We believe Methods provide a much better primitive for building modern applications than REST endpoints built on HTTP. Let's go over some of the things you get for free with Methods that you would have to worry about if using HTTP. The purpose of this section is not to convince you that REST is bad - it's just to remind you that you don't need to handle these things yourself in a Meteor app.我们相信Methods提供了更多的构建现代应用的要素,比较于构建在HTTP之上的REST终端。让我们看一些你通过Methods可以免费获得但在使用HTTP(的REST)时却需要去担心的事情。本节的内容可能还不能说服你,REST是不好的 — 它只是提醒你,在流星中你不需要去处理这些事情,(这是我们的目的)。

Methods使用 同步 — 风格 的APIs,但却是 非 — 阻塞式 的

你可能注意到在上面例子的Method中,我们并不需要在和MongoDB交互的时候撰写任何的回调,但Method仍然具有非阻塞式的属性,这使人们联系到Node.js和回调风格的代码。流星使用同步库(叫做 Fibers)使你可以写能使用返回值和抛错误的代码,同时可以避免很多的嵌套回调。

Methods总是按顺序运行和返回值

When accessing a REST API, you will sometimes run into a situation where you make two requests one after the other, but the results arrive out of order. Meteor's underlying machinery makes sure this never happens with Methods. When multiple Method calls are received from the same client, Meteor runs each Method to completion before starting the next one. If you need to disable this functionality for one particularly long-running Method, you can use this.unblock() to allow the next Method to run while the current one is still in progress. Also, since Meteor is based on Websockets instead of HTTP, all Method calls and results are guaranteed to arrive in the order they are sent. You can also pass a special option wait: true to Meteor.apply to wait to send a particular Method until all others have returned, and not send any other Methods until this one returns.当访问一个REST API,你有时会运行在某种情况下,这时,你会发送两个请求,一个接着一个,但结果的到达却是无序的。流星底层机制可以保证在使用Methods时绝对不会发生这种情况。当多个从同一个客户端而来的Method调用被接受到,流星会依次运行每个Method(完成一个再开始一个)。如果你需要为某个特定的长时间运行的Method屏蔽这种功能,请使用 this.unblock() 来允许下一个Method和仍在运行过程中的Method同时运行。而且,由于流星是基于Websockets而不是HTTP,所有的Method调用以及结果会保证其到达顺序和发送顺序一致。你也可以传递一个特殊的选项叫做wait: trueMeteor.apply 来等待发送一个特殊的Method直到所有的其它Methods已经返回了,同时不发送任何其它的Methods直到这个Method返回。

Change tracking for Optimistic UI改变跟踪乐观UI

When Method simulations and server-side executions run, Meteor tracks any resulting changes to the database. This is what lets the Meteor data system roll back the changes from the Method simulation and replace them with the actual writes from the server. Without this automatic database tracking, it would be very difficult to implement a correct Optimistic UI system.当Method模拟和服务器端执行运行时,流星跟踪任意导致数据库改变的行为。这是为什么可以让流星的数据系统可以在Method模拟的状况中进行回滚并且替换成从服务器端实际的写入。没有这样的自动的数据库跟踪,这会非常困难去实现一个正确的乐观UI系统。

从另一个Method中调用一个Method

Sometimes, you'll want to call a Method from another Method. Perhaps you already have some functionality implemented and you want to add a wrapper that fills in some of the arguments automatically. This is a totally fine pattern, and Meteor does some nice things for you:有时,你将会要从另一个Method里调用一个Method。也许你已经有一些实现好了的功能并且你会要添加一个包装器自动填充这些参数。这是一个完全地好的模式,并且流星已经为你做了很多:

  1. Inside a client-side Method simulation, calling another Method doesn't fire off an extra request to the server - the assumption is that the server-side implementation of the Method will do it. However, it does run the simulation of the called Method, so that the simulation on the client closely matches what will happen on the server.在客户端的Method模拟中,调用另一个Method不会产生对于服务器的额外调用-假设服务器端Method实现会去这么做。然而,这确实是运行了被调用的Method的 模拟,以至于在客户端的模拟运行与服务器端发生的完全一致。
  2. Inside a Method execution on the server, calling another Method runs that Method as if it were called by the same client. That means the Method runs as usual, and the context - userId, connection, etc - are taken from the original Method call.在服务器端一个Method的执行,调用另一个Method运行那个方法就如它被相同的客户端调用一样。这意味着该Method如往常一样运行,并且其上下文-userIdconnection,等-会从原始的Method调用中获得。

一致的ID产生和乐观的UI

When you insert documents into Minimongo from the client-side simulation of a Method, the _id field of each document is a random string. When the Method call is executed on the server, the IDs are generated again before being inserted into the database. If it were implemented naively, it could mean that the IDs generated on the server are different, which would cause undesirable flickering and re-renders in the UI when the Method simulation was rolled back and replaced with the server data. But this is not the case in Meteor!当你从客户端的Method模拟中向Minimongo中插入文档时,每个文档的_id字段是一个随机的字符串。当Method调用执行在服务器端,IDs会被再次产生,这是先于插入数据库的。如果幼稚地去实现,会意味着在服务器端的IDs产生与客户端的不同,就会造成不想要的麻烦和在UI中的重新渲染,这是发生在当Method模拟被回滚并被替换成服务器端数据的时候。但这不是在流星中发生的情况!

Each Meteor Method invocation shares a random generator seed with the client that called the Method, so any IDs generated by the client and server Methods are guaranteed to be the same. This means you can safely use the IDs generated on the client to do things while the Method is being sent to the server, and be confident that the IDs will be the same when the Method finishes. One case where this is particularly useful is if you want to create a new document in the database, then immediately redirect to a URL that contains that new document's ID.每个流星Method调用都共享一个随机生成器种子通过客户端调用该Method,所以任何的通过客户端和服务器产生的IDs可以保证是一致的。就是说你可以安全地使用产生的IDs在客户端来做任何的事情,而Method会被发送到服务器,可以确信这些IDs当Method完成时还是完全相同一致的。一种情况在这里特别有用就是如果你想要创建一个新的文档在数据库中,接着立即转向到一个URL包含着新的文档ID。

Method 的重试

If you call a Method from the client, and the user's Internet connection disconnects before the result is received, Meteor assumes that the Method didn't actually run. When the connection is re-established, the Method call will be sent again. This means that, in certain situations, Methods can be sent more than once. This should only happen very rarely, but in the case where an extra Method call could have negative consequences it is worth putting in extra effort to ensure that Methods are idempotent - that is, calling them multiple times doesn't result in additional changes to the database.如果从客户端调用一个Method,并且用户在收到结果之前,因特网连接中断,流星假设该Method并没有实际运行。当连接重新被连上后,该Method调用将会被再次发送。也就是说,在特定的情况下,Methods能够被发送超过一次。这种情况应该非常罕见,但在这种情况下,额外的Method调用可能具有负面的结果,值得我们花费额外的努力来确保那些Method的idempotent-就是,调用它们多次不会导致额外的数据库变化,

Many Method operations are idempotent by default. Inserts will throw an error if they happen twice because the generated ID will conflict. Removes on collections won't do anything the second time, and most update operators like $set will have the same result if run again. The only places you need to worry about code running twice are MongoDB update operators that stack, like $inc and $push, and calls to external APIs.多数Method的操作是默认idempotent。插入如果发生两次将会抛出错误因为产生的ID将会冲突。在集合上进行第二次移除不会产生任何影响,多数的更新操作符像$set重新运行后将导致相同的结果。只有一个地方需要注意,就是,代码运行两次会造成MongoDB更新问题的操作符有,$inc$push,以及调用外部的API。

Historical comparison with allow/deny</h3>

The Meteor core API includes an alternative to Methods for manipulating data from the client. Instead of explicitly defining Methods with specific arguments, you can instead call insert, update, and remove directly from the client and specify security rules with allow and deny. In the Meteor Guide, we are taking a strong position that this feature should be avoided and Methods used instead. Read more about the problems with allow/deny in the Security article.

Historically, there have been some misconceptions about the features of Meteor Methods as compared with the allow/deny feature, including that it was more difficult to achieve Optimistic UI when using Methods. However, the client-side insert, update, and remove feature is actually implemented on top of Methods, so Methods are strictly more powerful. You get great default Optimistic UI just by defining your Method code on the client and the server, as described in the Method lifecycle section above.