查询(也称为查询实例)是由模型方法(如.find()
和.create()
)返回的可链接延迟对象。它们表示尚未完成的从数据库中获取或修改记录的意图。
var query = Zookeeper.find();
查询实例的目的是为使用模型提供方便、可链接的语法。诸如.populate()
、.where()
和.sort()
等方法允许您在发送到网络之前细化数据库调用。然后,当您准备好将查询发送到数据库时,您可以简单地await
它。
如果您正在使用不支持 JavaScript 的
await
关键字的旧版 Node.js,则可以使用.exec()
或.then()
+.catch()
。有关更多信息,请参阅下面的“Promise 和回调”部分。
大多数情况下,您不会将查询实例视为对象本身,而是将其视为与数据库通信语法的另一部分。事实上,您可能已经在您的 Sails 应用程序中使用了这些对象!如果是这样,以下语法应该看起来很熟悉
var zookeepers = await Zookeeper.find();
在此示例中,对Zookeeper.find()
的调用返回一个查询实例,但在使用await
关键字执行之前实际上什么也不做,然后结果被分配给zookeepers
变量。
当您使用await
执行查询时,会发生很多事情。
await query;
首先,查询由 Waterline 核心“分解”为标准化查询。然后它通过相关的 Waterline 适配器进行转换,以转换为数据库的原始查询语法(例如 Redis 或 Mongo 命令、各种 SQL 方言等)。接下来,每个涉及的适配器使用其本机的 Node.js 数据库驱动程序通过网络将查询发送到相应的物理数据库。
当适配器收到响应时,它会被编组到 Waterline 接口规范并传回 Waterline 核心,在那里它与任何其他原始适配器响应集成到一个连贯的结果集中。在这一点上,它会进行最后一次规范化,然后再传递回“用户空间”(即您的代码),供您的应用程序使用。
如果需要,您可以使用 try/catch 处理特定的错误
var zookeepersAtThisZoo;
try {
zookeepersAtThisZoo = await Zookeeper.find({
zoo: req.param('zoo')
}).limit(30);
} catch (err) {
switch (err.name) {
case 'UsageError': return res.badRequest(err);
default: throw err;
}
}
return res.json(zookeepersAtThisZoo);
您可能收到的特定错误类型取决于您正在执行的查询类型。有关更具体的信息,请参阅各种查询方法的参考文档。
作为await
的替代方案,Sails 和 Waterline 提供了对回调和 Promise 链的支持。一般来说,您应该尽可能使用await
;它会导致更简单、更容易理解的代码,并有助于防止由于在异步回调中抛出未捕获的异常而可能出现的 DDoS 漏洞和稳定性问题。也就是说,有时需要保持与旧版 Node.js 的向后兼容性。出于这个原因,Sails 和 Waterline 中的所有查询都公开了一个.exec()
方法。
Zookeeper.find().exec(function afterFind(err, zookeepers) {
// Careful! Do not throw an error in here without a `try` block!
// (Even a simple typo or null pointer exception could crash the process!)
if (err) {
// uh oh
// (handle error; e.g. `return res.serverError(err)`)
return;
}
// would you look at all those zookeepers?
// (now let's do the next thing;
// e.g. `_.reduce(zookeepers, ...)` and/or `return res.json(zookeepers)`)
// …
});
//
// (don't put code out here)
如上例所示,查询不会立即执行,但请注意,我们没有使用await
执行查询并等待其结果,而是使用了传统的.exec()
方法和回调函数。使用这种用法,我们无法依靠 JavaScript 中的 try/catch 和正常的错误处理来处理我们的错误!相反,我们必须在我们的回调中手动处理它们以.exec()
。这种错误处理风格是 Node.js 应用程序在 2017 年夏季之前使用的传统方法。
在幕后,Sails 和 Waterline 还提供了与Bluebird Promise 库的最小集成,公开了.then()
和.catch()
方法。
Zookeeper.find()
.then(function (zookeepers) {...})
.catch(function (err) {...});
//
// (don't put code out here)
在此示例中,传递给.catch()
的回调等效于.exec()
示例中if(err) {}
块的内容(例如res.serverError()
)。类似地,.then()
回调等效于if(err) {}
和早期return
下面的代码。
如果您是 Promise 的粉丝并且对它们有相当多的经验,那么您应该能够毫无问题地使用此接口。但是,如果您不太熟悉 Promise,或者并不关心哪种方式,那么您可能会更容易使用.exec()
,因为它使用标准的 Node.js 回调约定。
如果您决定在应用程序的特定查询中使用传统的 Promise 链,请确保您为
.then()
和.catch()
都提供了回调。否则,错误可能会未处理,并且可能会发生令人不快的竞争条件和内存泄漏。这不仅仅是 Sails 或 Waterline 的概念。相反,在您在 JavaScript 中实现此类用法时(尤其是在 Node.js 中)需要注意这一点,因为服务器端代码中的未处理错误往往比客户端代码中的错误更成问题。省略.catch()
等效于忽略传统 Node 回调中的err
参数,并且同样具有阴险性。事实上,这是所有技能水平的 Node.js 开发人员最常见的错误来源之一。如果您不熟悉异步代码,那么正确的错误处理特别容易被忽略。一旦您使用了一段时间,您就会养成在处理成功案例的代码之后(甚至更好的是,在编写处理成功案例的代码之前)立即处理异步错误的习惯。这样的习惯可以让您的应用程序免受上面讨论的那些常见错误的影响。
(更好的是:只需使用
await
!)
- 查询实例并非完全等同于 Promise,但对于我们的目的来说已经足够接近了。区别在于,Sails 和 Waterline 中的查询实例实际上是一个 Deferred,由parley库实现。这意味着它不会立即开始执行。相反,只有当您使用
await
、.exec()
、.then()
或.toPromise()
启动它时,它才会开始执行。- Node 样式回调可以直接作为模型方法的最后一个参数传递(例如
.find()
)。在这种情况下,查询将立即执行,并且模型方法不会返回查询实例(而是,当查询完成时,您提供的 Node 样式回调将被触发)。除非您正在执行非常高级的操作,否则您通常最好坚持使用标准用法;即调用.exec()
或调用.then()
和.catch()
。