查詢(又稱查詢實例)是從模型方法(如 .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()
的回呼中手動處理它們。這種錯誤處理風格是 ~2017 年夏天之前 Node.js 應用程式中使用的傳統方法。
在底層,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 中的查詢實例實際上是由 parley 庫實作的 Deferred。這表示它不會立即開始執行。相反,它只會在您使用
await
、.exec()
、.then()
或.toPromise()
啟動它時才開始執行。- Node 風格的回呼可以直接作為最終引數傳遞給模型方法(例如
.find()
)。在這種情況下,查詢將立即執行,並且模型方法將不會返回查詢實例(相反,當查詢完成時,您提供的 Node 風格回呼將被觸發)。除非您正在做一些非常進階的事情,否則您通常最好堅持標準用法;即呼叫.exec()
或呼叫.then()
和.catch()
。