迁移到 Meteor 2.8
Meteor 2.8
引入了新的 MongoDB 包异步 API。有关更改的完整细分,请参阅更改日志。
对于这个新的异步 API,我们有像 findOneAsync
这样的新方法,它的行为与 findOne
方法完全相同,但现在返回一个需要解析才能获取数据的 Promise。
为什么这个新的 API 很重要?
您可能知道,在 Meteor 中,我们使用一个名为Fibers 的包。这个包使得在 Meteor 中以同步方式(无需等待 Promise 解析)调用异步函数(如 db.findOne()
)成为可能。
但是从 Node 16 开始,Fibers 将停止工作,因此 Meteor 需要远离 Fibers,否则我们将停留在 Node 14 上。
如果您想了解更多关于计划的信息,可以查看此讨论。
为什么现在进行此更改?
这对旧的 Meteor 应用程序来说将是一个相当大的变化,任何 Meteor 应用程序的某些代码部分最终都必须进行调整。因此,现在开始迁移过程非常重要。
使用此版本,您将能够通过将当前的 MongoDB 方法替换为新的异步方法来开始为将来做好准备。
我可以在不更改应用程序的情况下更新到此版本吗?
是的。您可以在不更改应用程序的情况下更新到此版本。
有什么新功能?
以下是新添加的方法(您可以在此处查看此描述和代码)
在集合上添加了异步方法。
- 所有异步方法在其名称后都带有 Async 后缀。它们是:
createCappedCollectionAsync
、createIndexAsync
、dropCollectionAsync
、dropIndexAsync
、findOneAsync
、insertAsync
、removeAsync
、updateAsync
和upsertAsync
。
在游标上添加了异步方法。
- 所有异步方法在其名称后都带有 Async 后缀。它们是:
countAsync
、fetchAsync
、forEachAsync
和mapAsync
。 - 还有
[Symbol.asyncIterator]
,因此此代码应该可以工作for await (const document of collection.find(query, options)) /* ... */
还有一个名为 callAsync
的新 Meteor 方法。它应该用于调用异步方法。
我如何开始使用这些新功能?
我们提供了一些示例,使这些新功能更容易使用,您可以查看下面的代码片段
// SERVER
// Before 2.8, we would use something like this
export const removeByID = ({ id }) => {
SomeCollection.remove({ _id: id });
};
// Now we can also do like this
export const removeByIDAsync = async ({ id }) => {
await SomeCollection.removeAsync({ _id: id });
};
Meteor.methods({
//...
removeByID,
removeByIDAsync,
});
// CLIENT
const result = Meteor.call('removeByID', { id });
// For the async, you call it like this:
const result = await Meteor.callAsync('removeByIDAsync', { id });
// or even like this:
Meteor.callAsync('removeByIDAsync', { id }).then(result => {
console.log(result);
});
新的 callAsync
如前所述,callAsync
应该用于调用异步方法。
我们不认为此版本的 callAsync
是最终产品。当您的方法具有存根时,它存在一些限制,并且应该在您知道它不会影响应用程序的其他部分的地方使用。
我们将很快重新审视这些限制,并尝试找到一个不存在这些限制的解决方案。
callAsync 限制
如果您有两个具有存根的方法,则如果第一个方法的存根仍在运行,则永远不要调用第二个方法。您需要确保每次只有一个存根在运行。例如
// This is ok, because methodWithoutStubAsync and methodWithoutStub
// does not have a stub inside them:
Meteor.callAsync('methodWithoutStubAsync', { id }).then(result => {
// do something
});
Meteor.call('methodWithoutStub', { id }, (error, result) => {
// do something
})
// This is ok as well, because even though removeByIDAsync has a stub,
// methodWithoutStub does not have one, so both methods can run
// at the same time:
Meteor.callAsync('removeByIDAsync', { id }).then(result => {
// do something
});
Meteor.call('methodWithoutStub', { id }, (error, result) => {
// do something
})
// This is also ok, because even though removeByID has a stub
// (SomeCollection.remove({ _id: id })), it is a sync method,
// so by the time removeByIDAsync runs, no stub will be running:
Meteor.call('removeByID', { id }, (error, result) => {
// do something
});
Meteor.callAsync('removeByIDAsync', { id }).then(result => {
// do something
});
// But, this is NOT ok, because you would have 2 stubs running at the same time:
Meteor.callAsync('removeByIDAsync', { id }).then(result => {
// do something
});
Meteor.call('removeByID', { id }, (error, result) => {
// do something
});
// instead, do it like this:
await Meteor.callAsync('removeByIDAsync', { id });
Meteor.call('removeByID', { id }, (error, result) => {
// do something
});
// or this
Meteor.callAsync('removeByIDAsync', { id }).then(result => {
// do something
Meteor.call('removeByID', { id }, (error, result) => {
// do something
});
});
由于 callAsync
返回一个 Promise,因此将来会解决此问题。因此,如果您需要在调用另一个方法(异步或非异步)之前等待它完成,则另一个方法需要具有存根。
如果您想了解为什么存在此限制,可以阅读此评论,该评论位于创建
callAsync
的 PR 中。
使用 Meteor.call 调用异步方法,反之亦然
了解如果使用 Meteor.call
调用异步方法,反之亦然会发生什么也很重要。
如果您在客户端使用 Meteor.call
调用异步方法,并且您的项目中没有 insecure
包,则会抛出类似这样的错误
抛出此错误是因为当 Meteor.call
执行您的异步方法时,该方法将返回一个 Promise。然后,当您的方法在将来运行时,它将不再具有全局上下文。这意味着,例如,如果您在异步方法中有一些 MongoDB 方法,它将在存根之外运行。
它等同于直接在客户端运行以下内容:SomeCollection.remove({ _id: id })
。因此,出现错误。如果您的项目中包含 insecure
包,则此错误不会显示,因为 insecure
允许您的应用程序从客户端运行写入方法。
在服务器端,可以使用 Meteor.call()
调用异步方法是可以的。
关于 Meteor.callAsync()
,使用同步方法从客户端或服务器调用它都可以。
不同组件中的方法
可能很难缩小应用程序中两个方法可能在同一时间调用的位置。Meteor 可以以多种方式使用。但是我们可以看到的一种情况将非常普遍,那就是当您有两个不同的组件调用两个方法时,例如
// this is a React example
const MyComponent1 = () => {
...
// If the user do not type anything in 5 seconds
// set its status to offile
useEffect(() => {
const interval = setInterval(() => {
const now = new Date();
const timeWithoutTexting = now.getTime() - lastType.getTime();
if (isUserOn && timeWithoutTexting >= 5000) {
Meteor.callAsync("changeUserStatus", 'OFFLINE');
}
}, 1000);
return () => clearInterval(interval);
}, [isUserOn, lastTyped]);
return <div>
<input onChange={async ({ target: { value }}) => {
// Every time the use type something, save the value in database,
// change the user status to online, and set a new lastTyped:
await Meteor.callAsync("updateText", value);
if (userStatus === 'OFFLINE') {
Meteor.callAsync("changeUserStatus", 'ONLINE');
}
setLastTyped(new Date());
}}/>
</div>
}
总结此示例,每次用户键入内容时,都会调用一个方法将新值保存到数据库中,并更改另一个方法以更改其状态(如果它们处于脱机状态)。一个作业还将每秒检查一次用户在过去 5 秒内是否未键入任何内容。
在此示例中,根据用户键入的速度,您最终可能会同时调用这两个方法,或者在另一个方法的存根仍然存在时调用其中一个方法。这意味着您的代码将无法正常工作。
这里避免此问题的一种策略是在 updateText
方法中将用户状态更改为 ONLINE,而不是调用方法来执行此操作。
更新用户状态为 OFFLINE 的作业也是如此。您可以在服务器端创建此作业,在客户端只保留对 updateText
方法的调用。
我们对未来的建议
我们建议您从本版本开始使用异步编写新的方法和发布,随着时间的推移,强制内部 API 也变为异步。当然,还要更新您当前的方法。您越早开始越好。
但请谨慎操作,并牢记上面提到的情况。
从低于 2.8 的版本迁移?
如果您要从低于 Meteor 2.8 的 Meteor 版本迁移,则可能需要考虑本指南中未列出的重要事项。请查看旧的迁移指南以获取详细信息
- 迁移到 Meteor 2.7(从 2.6 开始)
- 迁移到 Meteor 2.6(从 2.5 开始)
- 迁移到 Meteor 2.5(从 2.4 开始)
- 迁移到 Meteor 2.4(从 2.3 开始)
- 迁移到 Meteor 2.3(从 2.2 开始)
- 迁移到 Meteor 2.2(从 2.0 开始)
- 迁移到 Meteor 2.0(从 1.12 开始)
- 迁移到 Meteor 1.12(从 1.11 开始)
- 迁移到 Meteor 1.11(从 1.10.2 开始)
- 迁移到 Meteor 1.10.2(从 1.10 开始)
- 迁移到 Meteor 1.10(从 1.9.3 开始)
- 迁移到 Meteor 1.9.3(从 1.9 开始)
- 迁移到 Meteor 1.9(从 1.8.3 开始)
- 迁移到 Meteor 1.8.3(从 1.8.2 开始)
- 迁移到 Meteor 1.8.2(从 1.8 开始)
- 迁移到 Meteor 1.8(从 1.7 开始)
- 迁移到 Meteor 1.7(从 1.6 开始)
- 迁移到 Meteor 1.6(从 1.5 开始)
- 迁移到 Meteor 1.5(从 1.4 开始)
- 迁移到 Meteor 1.4(从 1.3 开始)
- 迁移到 Meteor 1.3(从 1.2 开始)