` 方法:从回调执行到底层原理完全指南)
后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载导读.exec()是 Sails 框架中 Waterline ORM 为链式查询实例query instance提供的最经典的执行方式它接收一个 Node 风格的(err, result)回调在查询完成无论成功还是失败后触发是await语法普及之前 Sails 应用中与数据库交互的标准姿势。本文以 docs/reference/waterline/queries/exec.md 为主体结合当前仓库中蓝图blueprints源码与集成测试用例完整讲解.exec()的语法、参数、示例、错误处理边界与底层执行机制并对比await、.then()、.toPromise()三种等效执行方式帮助你在不同 Node.js 版本和代码风格下做出正确选择。一、什么是.exec()查询实例的点火开关在 Sails / Waterline 中调用模型方法如Zookeeper.find()、User.create()返回的并不是查询结果而是一个查询实例query instance——官方文档将其定义为尚未兑现意图的、可链式的 deferred 对象见 docs/reference/waterline/queries/queries.md。这意味着var query Zookeeper.find(); // 此刻并未访问数据库query本身不发起任何数据库请求。你可以在它上面继续链式调用.where()、.sort()、.populate()、.limit()、.meta()等方法来精化查询直到调用.exec()或await、.then()、.toPromise()时才真正把查询发射到数据库。核心事实.exec()的作用就是执行一个 Waterline 查询实例Execute a Waterline query instance并传入一个查询完成后被调用的 Node 风格回调。如果不运行.exec()、也不用 promise 或await你的查询将永远不会执行——这是 Sails 官方文档在 exec.md 的 Notes 中明确强调的行为。二、语法与参数详解.exec()的用法极其简洁只需一个回调函数.exec(function (err, result) { })参数表ArgumentTypeDetails1callback((function))查询完成成功或失败后被调用的 Node 风格回调回调参数ArgumentTypeDetails1err((Error?))发生的错误如果没有错误则为undefined2result((Ref?))数据库返回的结果如果有。具体数据类型取决于查询类型如果发生错误即err为真值应忽略该结果参数关于result的取决于查询类型可以结合 Waterline 各查询方法文档理解.find()通常返回记录数组.findOne()返回单条记录或undefined.create()默认不返回新记录需配合.meta({ fetch: true })或链式.fetch()才会返回参见 docs/reference/waterline/queries/fetch.md.destroy()、.update()等写操作的结果形态同样受.fetch()/.meta()影响。三、完整示例在控制器中查询并返回原文档给出的示例是最典型的控制器用法——查询所有 Zookeeper 记录并返回 JSONZookeeper.find().exec((err, zookeepers){ if (err) { return res.serverError(err); } // would you look at all those zookeepers? return res.json(zookeepers); }); // // (dont put code out here)注意示例末尾的注释(dont put code out here)回调外的代码会在回调触发之前执行因此任何依赖查询结果的处理都必须写在回调内部。这是异步编程最容易踩的坑。带条件的查询示例结合其他链式方法.exec()可以承接任意复杂的查询意图Zookeeper.find({ zoo: san-diego }) .sort(name ASC) .limit(30) .exec(function (err, zookeepers) { if (err) { return res.serverError(err); } return res.json(zookeepers); });仓库中的真实用法蓝图动作.exec()并非仅存在于用户代码中——Sails 框架自身的 REST 蓝图动作blueprint actions就大量使用它。以find蓝图动作为例lib/hooks/blueprints/actions/find.js 中的核心调用链为Model .find(queryOptions.criteria, queryOptions.populates).meta(queryOptions.meta) .exec(function found(err, matchingRecords) { if (err) { // 如果是从 Waterline 返回的用法错误例如非法 criteria返回 400 // 否则是意外错误返回 500。 switch (err.name) { case UsageError: return res.badRequest(formatUsageError(err, req)); default: return res.serverError(err); } } // ...socket 订阅、res.ok(matchingRecords) 等 });这段源码印证了两个重要实践.exec()的err回调参数是错误处理的唯一入口——框架自身的蓝图逻辑同样依赖它依据err.name区分错误类型是标准做法UsageError用户查询参数错误映射为 400其余错误映射为 500。formatUsageError负责把底层错误转换成对终端用户更友好的信息见 lib/hooks/blueprints/formatUsageError.js。同样的模式还出现在 lib/hooks/blueprints/actions/create.js、lib/hooks/blueprints/actions/update.js、lib/hooks/blueprints/actions/destroy.js 等所有 CRUD 蓝图动作中。四、.exec()与await、.then()的三种等效执行方式Sails 官方在 exec.md 中明确指出As of Sails v1 and Node.js v8, you can take advantage ofawaitinstead of using this method.也就是说.exec()是await出现之前的传统写法三种方式在功能上完全等效// 方式一awaitSails v1 / Node.js v8 推荐 var zookeepers await Zookeeper.find(); // 方式二.exec() 回调传统 Node 风格 Zookeeper.find().exec(function (err, zookeepers) { /* ... */ }); // 方式三promise 链 Zookeeper.find() .then(function (zookeepers) { /* ... */ }) .catch(function (err) { /* ... */ });为什么官方推荐优先使用await根据 docs/reference/waterline/queries/queries.md 的说明await能带来更简单、更易读的代码并且有助于防止 DDoS 攻击与稳定性问题——因为异步回调中抛出未捕获异常哪怕是简单的拼写错误或空指针可能导致进程崩溃。当项目必须兼容不支持await的旧版 Node.js 时才需要回退到.exec()或.then().catch()。.then()/.catch()与.exec()的等价关系.catch()回调等价于.exec()示例中的if (err) { ... }块例如res.serverError(err).then()回调等价于if (err)之后、提前return之前的成功处理代码。使用 promise 链时务必同时提供.then()和.catch()否则错误可能无人处理引发竞态条件与内存泄漏——这是所有 Node.js 开发者最常见的 bug 来源之一详见 docs/reference/waterline/queries/then.md 与 queries.md 的 Notes。.toPromise()另一个替代品如果出于某种原因需要返回一个 Promise 对象例如接入其他基于 Promise 的库可以使用.toPromise()var promise Zookeeper.find().toPromise();它是.exec()的替代方案将查询实例转换为 Promise参见 docs/reference/waterline/queries/toPromise.md。五、底层原理查询实例实际上是 Deferredparley一个常见误解是把查询实例当作 Promise。Sails 官方文档澄清了这一点A query instance in Sails and Waterline is actually a Deferred, as implemented by the parley library. That means it doesnt start executing immediately. Instead, it only begins executing when you kick it off with eitherawait,.exec(),.then(), or.toPromise().来源docs/reference/waterline/queries/queries.md 的 NotesDeferred 与 Promise 的关键区别在于不会立即开始执行必须由上述四种方式之一踢一脚才启动。当查询被真正执行时Waterline 内部会发生一系列动作Waterline 核心将查询抖开为一个规范化查询normalized query查询传递给相应的 Waterline adapter被翻译成目标数据库的原生查询语法如 Redis / Mongo 命令、各类 SQL 方言每个 adapter 通过其 Node.js 原生驱动把查询经网络发送到对应物理数据库adapter 收到响应后按 Waterline 接口规范封送回 Waterline 核心与其它原始响应整合为一致的结果集再做最后一次规范化最终交回用户代码消费。在当前仓库中.exec()还直接驱动着视图渲染与参数解析等内部路径例如 lib/hooks/views/render.js 与 lib/hooks/request/params.all.js 都使用了 parley 风格的 deferred 机制。六、.exec()中的错误处理最佳实践由于.exec()走的是传统 Node 回调风格无法依赖 JavaScript 的try/catch来兜底必须在回调中手动处理错误。官方文档给出了明确的警告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 lets do the next thing; // e.g. _.reduce(zookeepers, ...) and/or return res.json(zookeepers)) });实践要点总结先检查err再使用result文档明确规定如果err为真值应忽略result参数不要在回调中裸抛异常除非有try块保护否则可能直接导致 Node.js 进程崩溃按err.name分流如蓝图源码所示UsageError用 400 响应其余错误用 500 响应保持提前return的习惯在错误分支处理完毕后立即return避免继续执行成功逻辑。在集成测试中的实战印证当前仓库的集成测试大量使用.exec()作为断言载体例如 test/integration/hook.blueprints.restful.routes.test.js 中sailsApp.models.pet.createEach(instancesToCreate).meta({ fetch: true }).exec(function(err, pets) { // 在回调中基于 err / pets 进行断言 }); sailsApp.models.user.findOne({ id: 1 }).exec(function(err, user) { // 查询完成后校验 user 记录 });这些测试模式本身也是学习.exec()用法的绝佳参考create类写操作配合.meta({ fetch: true })获取返回记录读操作直接在回调中消费结果。七、结合.intercept()与.tolerate()的现代错误处理从 Sails v1 起即使使用await执行查询也推荐用链式方法.intercept()和.tolerate()精细化处理预期错误它们是.exec()回调中if (err)分支的声明式替代.intercept(filter, handler)捕获指定错误或全部错误由 handler 返回修改后的 Error、新 Error或 actions2 的特殊退出信号exit signal来替换抛出的错误参见 docs/reference/waterline/queries/intercept.md.tolerate(filter, handler)吞掉指定错误改用 handler 的返回值作为查询结果缺省时为undefined不抛出参见 docs/reference/waterline/queries/tolerate.md。var newUserRecord await User.create({ emailAddress: inputs.emailAddress, fullName: inputs.fullName, }) .intercept(E_UNIQUE, (){ return new Error(There is already an account using that email address!) }) .fetch();简写形式.intercept(E_UNIQUE, emailAlreadyInUse)等价于传入返回该字符串的 handler在 actions2 中可直接作为 exit signal 使用。八、注意事项与常见陷阱速查综合 exec.md 的 Notes 与上述源码分析整理如下不执行就不会发生只调用Zookeeper.find()而不接.exec()/await/.then()/.toPromise()查询永远不会发往数据库回调外代码先于回调执行(dont put code out here)区域内的代码无法使用查询结果Node 风格回调可直接传给模型方法如Zookeeper.find(cb)会立即执行查询此时模型方法不再返回查询实例。除非做非常高级的操作官方建议坚持标准用法——链式构建查询后调用.exec()错误必须手动处理.exec()回调风格无法享受try/catch遗漏错误处理可能引发进程崩溃或难以排查的异步 bug写操作默认不返回记录需要.meta({ fetch: true })或.fetch()才能拿到被写入的记录错误地假设result形态是新手常见误区版本前提await替代方案要求 Sails v1 且 Node.js v8 及以上.exec()本身无此限制适用于任何版本。九、延伸阅读导航查询实例总览Working with queriesDeferred 机制、三种执行方式、错误处理完整讨论.then()与.toPromise()另两种等效执行方式.intercept()与.tolerate()声明式错误处理.fetch()、.meta()控制查询结果形态与底层行为源码蓝图动作实现 lib/hooks/blueprints/actions/find.js、错误格式化工具 lib/hooks/blueprints/formatUsageError.js测试集成测试 test/integration/hook.blueprints.restful.routes.test.js 中大量.exec()实战用法一句话总结.exec()是 Waterline 查询实例最正统的 Node 风格执行入口——在 Sails v1 / Node.js v8 的新项目中优先使用await而在维护旧代码或必须兼容旧环境时err优先、绝不裸抛、按err.name分流的.exec()回调写法依然是最可靠的选择。赞分享后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载相关推荐Sails Waterline 查询实例的 Promise 执行方法 .then()用法、链式调用与底层原理Sails Waterline 查询实例的 Promise 执行方法 .then 用法、链式调用与底层原理 导读 .then 是 Sails Waterlin后端Sails/Waterline 查询方法 .limit() 完全指南限制查询返回记录数Sails/Waterline 查询方法 .limit 完全指南限制查询返回记录数 .limit 是 Sails 内置 ORM Waterline 查询实例后端Sails 模型方法 .count() 完全指南Waterline 记录计数的用法、语法与底层原理Sails 模型方法 .count 完全指南Waterline 记录计数的用法、语法与底层原理 本指南以 Sails v1.x仓库版本 1.5.18见 p后端上一篇Luckysheet API 全指南单元格、行列、选区、工作表与工作簿操作实战详解下一篇Mastodon内容过滤革命IceCubesApp如何智能应用服务器端规则创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考