---
title: 升级到Parse 3.1版本的技术要点与开发者指南
slug: docs-ch/advanced-guides/parse-server-3
description: 探索Parse 3.1新特性，优化云代码，提升应用性能，简化开发流程。详细技术步骤确保顺利迁移。
image: https://www.back4app.com/_public/img/back4app-og.png
createdAt: 2024-01-26T13:12:41.820Z
---

# 为您的应用程序提供更多功能，使用 Parse Server 3.1

## 介绍

Parse 社区最近发布了版本 [3.1 的 Parse Server](https://docs.parseplatform.org/parse-server/guide/). 此次更新清理了云代码语法：它更容易利用 es6 的 async 和 await 结构。

此外，与使用 Parse 相关的一些特性被去掉，例如，云函数简单地返回一个 Promise，而不是在响应对象上使用 <font color="#2166ae">`错误`</font> 或 <font color="#2166ae">`成功`</font> 消息。

您可以在 Back4App 的仪表板上轻松升级您的应用程序。本指南将演示如何升级您的代码以利用 3.1 的新功能。

:::hint{type="info"}
要遵循本指南，欢迎您查看提供的 [示例项目](https://github.com/back4app/parse-server-break-changes)。
:::

这是由 [John Considine](https://github.com/considine), [K-Optional](https://koptional.com/) 的首席开发人员撰写的客座教程。

## 目标

- 将您的 Back4App Parse Server 更新到 3.1，并相应地迁移您的云代码。

## 先决条件

:::hint{type="info"}
要完成本教程，您需要：
° 一个现有的使用 Parse Server 2.x 的 Back4App 应用程序
&#x20;        °    请遵循 [创建新应用教程](https://www.back4app.com/docs/get-started/new-parse-app) 以了解如何在 Back4App 上创建应用。
:::

## 更改摘要

最显著的变化如下：

**&#x20;1. 云代码与 Parse-SDK 2.x 一起运行。**&#x20;

之前，云代码与 Parse-SDK 1.x 一起运行。随着 Parse Server 3.1，它运行 Parse-SDK 2.x。

:::hint{type="info"}
查看 [Parse SDK 发布](https://github.com/parse-community/Parse-SDK-JS/releases) 以更好地理解这意味着什么。这个主要版本的提升主要涉及错误修复。它还添加了 [containedBy](https://parseplatform.org/Parse-SDK-JS/api/2.1.0/Parse.Query.html#containedBy) 和 [includeAll](https://parseplatform.org/Parse-SDK-JS/api/2.1.0/Parse.Query.html#includeAll) 查询方法，以及使用 includes 获取对象的能力。
:::

&#x20;**2**. **聚合更新**

自 Parse 2.7.1 起，您可以在查询上使用 [aggregate](https://parseplatform.org/Parse-SDK-JS/api/2.1.0/Parse.Query.html#aggregate) 方法。这使您能够更好地利用底层数据库。现在， [aggregate 方法](https://parseplatform.org/Parse-SDK-JS/api/2.1.0/Parse.Query.html#aggregate) 的语法在 <font color="#2166ae">`Parse.Query`</font> 上已更新。

您可以使用两个阶段执行查询： <font color="#2166ae">`match`</font> 和 <font color="#2166ae">`group`</font> 阶段。

:::BlockQuote
1    // 匹配名称为 Foo 的用户，并按其 objectId 分组
2    const pipeline = \[\{ group: \{ objectId: \{} } }, \{ match: \{ name: 'Foo' } }];
3
4    var query = new Parse.Query("Person");
5    query.aggregate(pipeline)
:::

之前，您不需要在管道对象中包含 pipeline 键。由于底层 API，您现在必须明确包含 pipeline 键。其值应为一个或两个阶段的数组，包含 <font color="#2166ae">`group`</font> 和 <font color="#2166ae">`match`</font>.

:::hint{type="info"}
查看 [Parse 官方文档](https://docs.parseplatform.org/js/guide/#aggregate)以获取更具体的示例。
:::

**3**. **底层优化**

一些底层优化已经完成。例如，[Parse LiveQuery](https://parseplatform.org/Parse-SDK-JS/api/2.1.0/Parse.LiveQuery.html) 将获取类级权限 (CLPs) 以及数据，以防止双重数据库访问。

**4. Parse 重置电子邮件**

当请求密码重置邮件时，即使该邮件未被保存，服务器也会返回成功。此外，当用户的电子邮件被重置时，密码重置令牌会过期。

**5.** **云触发器更新**

通过此版本，您可以在同一对象之间共享数据，[beforeSave](https://docs.parseplatform.org/cloudcode/guide/#beforesave-triggers) 和 [afterSave](https://docs.parseplatform.org/cloudcode/guide/#aftersave-triggers) 触发器。例如：

```javascript
1    Parse.Cloud.beforeSave('Comment', async request => {
2      request.context = {
3        foo: 'bar'
4      };
5    });
6
7    Parse.Cloud.afterSave('Comment', async request => {
8      console.log(request.context.foo); //Bar
9    });
```

:::hint{type="info"}
您可以通过点击查看官方 Parse 3.1 更新日志中的 Parse Server 变更[ 这里](https://github.com/parse-community/parse-server/blob/alpha/CHANGELOG.md).
:::

**6. LiveQuery 改进**

Parse LiveQuery 客户端允许您订阅查询，并在服务器发送更新时接收更新。传统查询由客户端执行一次，因此这对于消息传递等情况非常有帮助。

使用 Back4App，您还可以 [利用这项技术](https://www.back4app.com/docs/platform/parse-server-live-query-example)。

随着 3.x 的发布，Parse 社区改善了 LiveQuery 的系统 [ACLs](https://docs.parseplatform.org/js/guide/#security-for-other-objects)。

您现在可以将会话令牌传递给实时查询的订阅方法，Parse 服务器将仅管理返回该用户可以访问的结果。例如，用户可能对某些‘消息’具有读/写访问权限，但并非所有消息。

```javascript
1   let query = new Parse.Query('Message');
2   // you can get session token with
3   // Parse.User.current().getSessionToken() when logged in
4   let subscription = client.subscribe(query, sessionToken); 
```

上述代码将自动订阅用户可以访问的所有消息，减轻了您查询特定消息的责任。

**更改的主要部分涉及云代码的处理。有关此内容，请参阅下面的迁移指南。**

## 1 - 对齐技术基础

我们将以一个使用2.x版本的示例云项目开始。这样我们可以导航到适当的语法更改。你可以看到 [该代码库](https://github.com/back4app/parse-server-break-changes) 作为这个示例项目的参考。

**如果你熟悉async和await，你可以跳过这一部分。**

Javascript中异步代码的演变大致如下：

- [回调函数](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)
- [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [Async / Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)

任何现代Javascript应用程序都可能使用这三者。回调函数涉及将一个函数作为参数传递给另一个函数。这个第二个函数可以在某个时刻执行第一个函数。

```javascript
1   // Callbacks:
2   // executes callback function after waiting 100 milliseconds
3   setTimeout(function() {
4     alert('My callback function');
5   }, 100);
```

回调是必不可少的，但在链接多个回调时可能会显得笨拙。具体来说，嵌套多个层次可能难以阅读，错误处理也很困难。因此在ES2015中引入了Promise。

```javascript
1   // promises
2   // executes several promises in a row with no significant nesting
3   const myPromise = new Promise(function(resolve, reject) {
4     setTimeout(function() {
5        if (Math.random() < 0.2) reject(new Error('Random failure!'));
6        resolve('Finished');
7     }, 100);
8   });
9
10  // Executes this promise 4 times and catches errors involved
11  myPromise
12    .then(() => myPromise)
13    .then(() => myPromise)
14    .then(() => myPromise)
15    .then(() => myPromise)
16    .catch(e => console.log(e));
```

Promise提高了异步编程的可读性。它们还使管道更加明确。但在 [ES2017](https://www.ecma-international.org/ecma-262/8.0/#sec-async-function-definitions) 中，使用async / await构造取得了更大的进步。您的代码现在可以等待Promise的结果，而无需依赖then / catch块（这也可能变得难以阅读）。

```javascript
1   // Using the definition of myPromise from the above code:
2   async function foo() {
3     try {
4       let result = await myPromise;
5       result = await myPromise;
6       result = await myPromise;
7     } catch (e) {
8       console.log(e);
9     }
10  }
```

也许对于一个非常简单的例子，这看起来并不比普通的promise更优雅。但等待异步函数的结果往往正是我们想要做的。因此，它确实优化了我们代码的可读性。

**对 async/await 的支持**

如前所述，async/await 被包含在 [ECMAScript 2017 规范](https://www.ecma-international.org/ecma-262/8.0) (es8)。对于服务器代码，版本控制几乎不是问题，因为您可以更新到 [支持这些特性的 Node.js 版本](https://node.green/#ES2017). 请放心，Back4App 的环境支持最近的稳定版本。对于浏览器代码，像 [Babel](https://babeljs.io/) 的转译器将生成与使用 async / await 的 es2016 兼容的代码，并在现代浏览器中工作。

## 2 - 以不同的方式思考您的代码

Cloud Code 的主要变化涉及开发者所做的与库所做的之间的区别。以前，您会显式管理响应。由于大多数 Cloud Code 将异步执行 - 进行数据库查询和写入 - 返回一个 Promise 更有意义，从而减少样板代码。

Cloud 函数的直觉是编写服务器端代码时涉及的设置和配置最小化。此版本体现了这一理念；在重构和创建新函数时请记住这一点。

为了展示 Cloud Code 函数在 Parse Server 3.1 中的工作原理，我们重写了一个来自迁移前版本的功能性 Cloud Code 示例。您可以通过点击 [这里](https://github.com/back4app/parse-server-break-changes/tree/step_1). 相同的 Cloud Code 函数在 Parse 3.1 中如下所示。

```javascript
1   // Cloud Code BEFORE migration
2   // Full code found in link above
3   const POST = 'Post';
4   Parse.Cloud.define('posts:get', function(request, response) {
5     // needs a post ID
6     return new Parse.Query(POST)
7       .get(request.params.id, { useMasterKey: true })
8       .then(post => {
9         response.success(post);
10      })
11      .catch(e => {
12        response.error({ message: e.message });
13      });
14  });
```

## 3 - 添加所有异步标记

任何使用 await 的函数必须声明为 async 修饰符。这个简单的重构将把 async 附加到所有云函数上。它还将用 [箭头函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 替换它们，因为在这种情况下它们更简洁（也是更新后的官方 Parse 指南所使用的）。

```javascript
1   // Snippet of step 2 code refactoring. See full code
2   //  here in the link at the top of this step
3   const POST = 'Post';
4   const COMMENT = 'Comment';
5
6   Parse.Cloud.define('posts:get', async (request) => {
7     // Needs a post ID
8     return new Parse.Query(POST)
9       .get(request.params.id, { useMasterKey: true });
10  }    
```

**您的代码在重构后将看起来像 [这个](https://github.com/back4app/parse-server-break-changes/tree/step_2) 重构后**

到目前为止没有什么疯狂的。在下一步中，我们将为这个变化获得物有所值。

## 4 - 移除对响应的引用，使用 await

**您的代码看起来像 [这样](https://github.com/back4app/parse-server-break-changes/tree/step_3) 在这个重构之后**

这一步有点棘手。我们需要：

- 删除对‘response’变量的所有引用，改为返回一个promise
- 在多个查询/保存函数的情况下，等待响应

查看评论创建方法以了解这是如何完成的

```javascript
1   // Snippet of step 3 code refactoring. See full code
2   //  here in the link at the top of this step
3   Parse.Cloud.define('comment:create', async request => {
4     // Post should have text and should have a user and a post id
5     if (!request.user) {
6       throw new Error('unauthenticated!');
7     }
8
9     if (!request.params.text) {
10      throw new Error('A comment needs text!');
11    }
12    if (!request.params.post_id) {
13      throw new Error('A comment needs a post!');
14    }
15
16    //   Get the post
17
18    const post = await new Parse.Query(POST).get(request.params.post_id, {
19      useMasterKey: true
20    });
21    return new Parse.Object(COMMENT, {
22      text: request.params.text,
23      user: request.user,
24      post: post
25    }).save(null, { useMasterKey: true });
26  });
```

:::hint{type="info"}
注意：


- 现在抛出一个JavaScript错误，而不是调用response.error。Parse将处理将其转换为响应的过程。
- 删除‘帖子’或‘评论’涉及先获取对象，然后销毁它。通过使用‘await’，销毁方法可以在块外访问保存的对象。
:::

这完成了迁移所需的所有重构。也就是说，如果你做到这一步，恭喜你！你的代码将在 Back4App Parse 3.1 上运行！

## 5 - 高级技巧（可选）

以下更改是可选的，但可以显著缩短你的代码。我发现它们减少了样板代码。

你可能注意到很多手动检查参数或经过身份验证的用户。这些确保不会发生奇怪的行为。例如，我们已经决定我们的‘Post’对象需要文本，因此如果没有传递‘text’参数，该对象将会在没有它的情况下被保存。防止这种情况的一种方法是检查是否传递了文本。

但是手动检查可能会耗时且不优雅。在这一部分，我们利用 [对象解构](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 来隐式完成这些检查。

如果你不知道什么是解构，你应该查看上面的文档。但简而言之，它允许你用非常简洁的语法将对象的属性转换为变量。

```javascript
1   // This
2   var obj = {
3     hi: true,
4     bye: false
5   };
6   var hi = obj.hi;
7   var bye = obj.bye;
8
9   // Is equivalent to this:
10  var obj = {
11    hi: true,
12    bye: false
13  };
14  var { hi, bye } = obj;
15
16  console.log(hi);
17  // true
18  console.log(bye);
19  // false
```

解构赋值比手动赋值更简洁。它还允许你即时声明参数变量，这很好:

```javascript
1   Parse.Cloud.define('posts:get', async ({ params: { id } }) => {
2     // id is declared
3   });
```

当与 es2015 语法结合使用时，我们可以优化我们的参数检查。

我们可以这样做:

```javascript
1   // Iterates through object's keys. Makes sure, for each key, the value is set
2   const AssertParams = parameter_obj => {
3     for (var key of Object.keys(parameter_obj)) {
4       if (typeof parameter_obj[key] === 'undefined')
5         throw new Error(`Missing parameter ${key}`);
6     }
7   };
8
9   var obj = {
10    hi: true,
11    bye: false
12  };
13  var { hi, undef, bye } = obj; // undef will be undefined
14  var check_1 = { hi, bye };
15  var check_2 = { hi, undef };
16
17  // check = { hi : true, no : undefined }
18  AssertParams(check_1); // passes
19  AssertParams(check_2); // throws error
```

所以对于我们的 Parse 代码，我们可以这样做：

```javascript
1   // Snippet of advanced code refactoring. See full code
2   //  here in the link at the top of this step
3   Parse.Cloud.define('posts:delete', async ({ user, params: { id } }) => {
4     // Makes sure user is authenticated, and id parameter is passed
5     AssertParams({ user, id });
6     const post = await new Parse.Query(POST).get(id, {
7       useMasterKey: true
8     });
9     return post.destroy({ useMasterKey: true });
10  });
```

如果这让你感到畏惧，不用担心。这个 [完整代码](https://github.com/back4app/parse-server-break-changes/tree/step_advanced) 示例可能会有所帮助。

简而言之，‘AssertParams’ 是一个实用函数，用于在值未定义时抛出错误。我们可以通过结合对象解构和 es2015 对象初始化来一次性检查参数。

这消除了八到九个手动检查，这在一段时间后会变得不太美观。

## 6 - 在 Back4App 上升级

一旦您迁移了代码，您还必须做两件事才能在 Back4App 上运行它。

1. 升级您的 Back4App 服务器版本
2. 上传您的新云代码。

我之前简要提到过第一步。您需要做的就是登录 Back4App 并进入您应用的仪表板。从那里，只需在左侧选择“服务器设置”，然后在“管理 Parse 服务器”卡上点击“设置”按钮。然后您可以选择 Parse Server 3.1.1 单选按钮并点击“保存”。

最后但同样重要的是，您可以通过 [Back4App CLI](https://blog.back4app.com/2017/01/20/cli-parse-server/), 或使用手动上传通过您的仪表板上传您的代码。如果您需要更多关于此步骤的信息，请参见 [本指南](https://www.back4app.com/docs/android/parse-cloud-code)。

**关于发布新版本的说明**

如果您的代码很复杂，最好在执行这两个步骤之前进行一些测试。Back4App 有一些关于如何做到这一点的指南，您可以在 [这里](https://www.back4app.com/docs/advanced-guides/parse-cloud-code-testing) 和 [这里](https://www.back4app.com/docs/cloud-code-functions/unit-tests) 找到。

最后，重要的是要注意这些变化是 **重大**。在 2.x Parse Server 上使用我们编写的代码将会失败，而在 3.1 服务器上使用 2.x 代码也会失败。因此，您必须确保在上传升级后的 Cloud Code 时立即升级您的 Back4App 解析版本。如果您有很多用户，并且担心代码上传和版本升级稍微不同步，您可以这样做。

```javascript
1   const serverVersion =
2     Parse.CoreManager.get('VERSION') === 'js1.11.1' ? 'OLD' : 'NEW';
3
4   if (serverVersion === 'NEW') {
5     Parse.Cloud.define('posts:get', async ({ params: { id } }) => {
6       AssertParams({ id });
7       // Needs a post ID
8       return new Parse.Query(POST).get(id, {
9         useMasterKey: true
10      });
11    });
12  } else if (serverVersion === 'OLD') {
13    // Old definition here
14  }
```

这段代码动态地确定版本并根据此定义 Cloud 函数。上传后，您将更改为 3.1，然后可以重新上传删除旧部分的代码。最初包含检查可以确保您的代码不会崩溃。由于您可以在几秒钟内升级和上传，因此通常没有必要。

## 结论

在本指南中，我们简单演示了如何将您的 Cloud Code 迁移到 Parse 3.1 版本。记得在进行这些更改后将您的 Back4App 版本提升到 3.1。

同样重要的是，本指南演示了 Parse 3.1 所利用的改进语法。我们的重构将代码库从约 160 行减少到约 90 行，并使其更易读。对于实际应用，这将带来回报。
