尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Sails 框架中 Waterline 查询实例的 `.toPromise()` 方法:原理、用法与最佳实践

Sails 框架中 Waterline 查询实例的 `.toPromise()` 方法:原理、用法与最佳实践 Sails 框架中 Waterline 查询实例的.toPromise()方法原理、用法与最佳实践【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails导读.toPromise()是 Sails基于 Node.js 的实时 MVC 框架内置 ORM——Waterline——为查询实例query instance提供的一种执行方式它接收一个由模型方法如.find()、.create()返回的链式查询对象立即开始执行查询并返回一个 Promise。本文将以 docs/reference/waterline/queries/toPromise.md 为骨架结合 lib、package.json 与相关参考文档完整讲解.toPromise()的语法、它与.exec()/.then()/await的关系、底层基于 parley 的 Deferred 实现机制以及在实际 Sails 应用中的最佳实践。读完本文你将能够熟练判断何时使用.toPromise()并掌握查询实例从构建、执行到错误处理的完整生命周期。一、.toPromise()是什么.toPromise()是 Waterline 查询实例上的一个方法。所谓查询实例指的是从模型方法如.find()、.create()、.update()返回的可链式调用的延迟对象chainable deferred objects它代表一个尚未真正执行、但意图已经明确的数据库读写请求。在 Sails 的 ORM 参考文档中.toPromise()的定义非常简洁开始执行一个 Waterline 查询实例并返回一个 promise。其用法语法为.toPromise();从语义上讲.toPromise()是.exec()的 Promise 化替代方案原文注释明确指出This is an alternative to.exec().。区别在于.exec(callback)使用 Node 风格回调err, result接收查询结果.toPromise()不接收任何参数执行查询后返回一个标准的 Promise 对象由调用方通过.then()/.catch()或await消费结果。换句话说.toPromise()将触发查询执行与结果回调解耦返回值是一个可以继续链式调用、可以传递给任意 Promise 组合工具如Promise.all()、Bluebird.promisify()等的 Promise 实例。二、查询实例的四种执行方式要理解.toPromise()的定位需要先了解查询实例的完整执行家族。根据 docs/reference/waterline/queries/queries.md查询实例在构建之后不会立即执行只有通过以下四种方式之一踢一脚kick it off查询才会真正发送到数据库执行方式语法结果处理awaitvar users await User.find();返回解析后的查询结果Sails v1 / Node.js v8 推荐.exec(callback)User.find().exec((err, users) {...})Node 风格回调errresult两个参数.then()/.catch()User.find().then(fn).catch(fn)Promise 链式回调基于 Bluebird 的极简集成.toPromise()User.find().toPromise()直接返回 Promise 对象交由调用方处理从 lib/hooks/views/render.js 的源码可以看到Sails 内部也大量使用parley这个库来包装这种延迟执行的语义require(parley)后被用于构造返回 Promise/回调兼容对象的函数。查询实例本质上就是一个由 parley 库实现的Deferred对象——这正是它在并不完全等于 Promise但用法上几乎一样的底层原因。注parley 是 Sails 生态中的一个小型工具库当前仓库 package.json 声明依赖parley: ^3.3.4它把Node 回调风格与Promise 风格统一封装在一个可延迟执行的句柄中。查询实例的.exec()、.then()、.toPromise()都是这个句柄暴露出的执行入口。执行时机查询是懒的无论使用哪种方式关键点都在于模型方法调用本身不会触发任何数据库操作。// 此时什么都不会发生只是构建了一个查询实例 var query Zookeeper.find({ name: leo }).limit(30); // 直到这里查询才真正被发送到数据库 var zookeepers await query;这一点在 lib 目录下的控制器、服务等业务代码中随处可见查询实例可以被存储、传递、组合然后在合适的时机统一执行。三、.toPromise()的完整用法示例.toPromise()的调用形式极为简单——它不接受任何参数直接返回 Promisevar promise Zookeeper.find({ zoo: san-diego }).sort(name ASC).toPromise(); promise.then(function (zookeepers) { // 查询成功zookeepers 是查询结果记录数组 console.log(Found, zookeepers.length, zookeepers); return res.json(zookeepers); }) .catch(function (err) { // 查询失败统一错误处理 return res.serverError(err); });由于返回的是标准 Promise.toPromise()的结果可以非常自然地融入现代 JavaScript 异步流程// 在 async 函数中使用 await 消费 .toPromise() 的结果 async function getZookeepers(req, res) { try { var zookeepers await Zookeeper.find({ zoo: req.param(zoo) }).toPromise(); return res.json(zookeepers); } catch (err) { return res.serverError(err); } } // 与 Promise.all 组合并行执行多个查询 var [ zookeepers, keepers ] await Promise.all([ Zookeeper.find().toPromise(), Keeper.find().toPromise() ]);与.then()的关系值得注意的细节是.toPromise()与.then()在底层都基于同一个 parley Deferred 实现。区别在于.then(onFulfilled)直接注册回调返回值仍是一个可继续链式调用的对象即查询实例本身继续充当 Promise.toPromise()不注册回调只返回 Promise把触发执行这件事本身留给你来决定如何消费。因此如果你想把查询实例当作一个纯粹的 Promise 值传递出去例如交给工具函数、返回给调用方、塞进Promise.all().toPromise()是最贴切的选择而如果你只是想在当前作用域内继续.then().catch()链式写法直接调用.then()即可二者可以互相替代。四、完整工作流程从查询构建到结果返回在 docs/reference/waterline/queries/queries.md 的 How it works 一节中详细描述了await以及等效的.toPromise()等执行入口触发后发生的完整链路归一化shaken outWaterline 核心把查询实例解析成一份归一化查询normalized query对应概念文档中的查询语言适配器翻译归一化查询被交给相关的 Waterline 适配器adapter翻译成目标数据库的原生查询语法如 Redis / Mongo 命令、各种 SQL 方言等网络发送每个适配器再使用其底层的原生 Node.js 数据库驱动driver把查询通过网络发送到对应的物理数据库结果回传适配器收到数据库响应后将其按 Waterline 接口规范进行编组marshalled回传给 Waterline 核心结果整合与再归一化Waterline 核心把所有适配器的原始响应整合成一个连贯的结果集经过最后一次归一化后交还给用户域userland——也就是你的业务代码。整个过程对调用者完全透明无论你用的是await、.exec()、.then()还是.toPromise()最终拿到的都是经过 Waterline 统一处理后的记录records或受影响的记录数等结果。五、错误处理.toPromise()与 try/catch 的配合由于.toPromise()返回 Promise其错误处理完全遵循 Promise 语义查询失败时返回的 Promise 会以 rejected 状态结束你可以用.catch()或awaittry/catch捕获。参考 docs/reference/waterline/queries/catch.md 中展示的按错误类型分诊模式可以写出健壮的错误处理var zookeepersAtThisZoo; try { zookeepersAtThisZoo await Zookeeper.find({ zoo: req.param(zoo) }).limit(30).toPromise(); } catch (err) { switch (err.name) { case UsageError: return res.badRequest(err); // 参数/用法错误 default: throw err; // 其余错误交给上层 } } return res.json(zookeepersAtThisZoo);错误类型概览根据查询方法的不同可能收到的错误类型也不同常见包括错误类型典型场景建议处理UsageError查询参数非法、模型属性不存在、.limit()传了负数等res.badRequest(err)或抛给上层数据库连接类错误数据源datastore不可达、连接超时记录日志并res.serverError(err)适配器/驱动错误SQL 语法错误、唯一约束冲突视业务决定是否向客户端暴露详细错误目录可参考 docs/concepts/ORM/errors.md。千万注意不要遗漏.catch()如果使用.toPromise()配合.then()链式写法必须同时提供.then()和.catch()。遗漏.catch()等价于在传统 Node 回调中忽略err参数——被吞掉的 Promise rejection 在服务端代码中尤其危险可能导致未处理的异常、难以排查的竞态条件和内存泄漏。这是 Node.js 开发者无论水平高低最常见的 bug 来源之一。若不想费心处理这些直接用await即可。六、底层原理parley 与 Deferred 模式Sails 与 Waterline 在 Promise 支持上并非自己实现了一套 Promise 规范而是通过 parley 库提供极简集成minimalist integration。查询实例在底层是一个 DeferredDeferred 与 Promise 的区别Promise 在构造时通常就已热立即开始执行而 Deferred 是冷的——它携带了执行所需的一切信息目标模型、过滤条件、排序、分页等但只有当你调用await、.exec()、.then()或.toPromise()时才开始真正执行。为什么说是极简集成参考文档明确指出查询实例的.then()/.catch()行为与 Bluebird Promise 库兼容可以配合Bluebird.promisify()等工具使用但 Sails 并不强制你引入 Bluebird——await原生即可。从源码证据看当前仓库 package.json 在 dependencies 中声明了parley: ^3.3.4并且 lib/hooks/views/render.js 中直接require(parley)来构造返回 Promise 的渲染函数。由此可以推断Waterline 的查询实例同样由 parley 生成Waterline 本体作为独立的 ORM 库被sails-hook-orm集成其查询对象的 Deferred 实现与 Sails 内部保持一致。这也是为什么.toPromise()、.then()、.exec()三种入口能够同时存在于同一个查询实例上——它们都是 parley Deferred 暴露的统一执行接口。三种执行入口的本质等价性执行入口回调风格返回底层机制.exec(cb)Node 风格(err, result)undefined或查询实例parley 触发执行调用回调.then(fn)Promise 风格可继续链式调用的查询实例parley 把查询包装成 Promise 后调用 fn.toPromise()无回调标准 Promiseparley 触发执行并返回 Promise三者共享同一套查询归一化 → 适配器翻译 → 驱动发送 → 结果编组回传的执行管线区别只在于结果交付方式。七、与.exec()的详细对比既然.toPromise()是.exec()的替代方案不妨通过 docs/reference/waterline/queries/exec.md 的规范逐一对比// 方式一.exec() Node 回调传统写法Node.js v8 之前的主流 Zookeeper.find().exec((err, zookeepers) { if (err) { return res.serverError(err); } return res.json(zookeepers); }); // 方式二.toPromise() Promise 链 Zookeeper.find().toPromise() .then(function (zookeepers) { return res.json(zookeepers); }) .catch(function (err) { return res.serverError(err); }); // 方式三.toPromise() await推荐 try { var zookeepers await Zookeeper.find().toPromise(); return res.json(zookeepers); } catch (err) { return res.serverError(err); }何时选哪种需要兼容非常老的 Node.js 环境不含await用.exec(cb)或.then()/.catch()想让代码最简洁、错误处理最稳妥直接用await查询实例可被直接 await甚至不需要显式调用.toPromise()需要把触发查询与消费结果分离将查询结果作为值传递例如塞入Promise.all()、返回给上层函数、交给 Bluebird 工具函数.toPromise()是最贴合意图的选择.exec()的回调内不要抛出异常除非有try块包裹——即使只是简单的拼写错误或空指针异常也可能导致进程崩溃这是传统回调风格在服务端代码中的固有风险而 Promise/await风格天然规避了这一点。参考 docs/reference/waterline/queries/queries.md 的建议能用await尽量用await它让代码更简单易读还能避免异步回调中抛出未捕获异常所引发的稳定性问题与 DDoS 风险。八、使用注意事项与最佳实践综合上述文档与源码分析整理出.toPromise()的使用要点不调用执行入口 查询不执行。构建查询实例后如果既不await、也不调用.exec()/.then()/.toPromise()查询永远不会发送到数据库也不会产生任何错误提示——这是新手最容易困惑的静默失效。.toPromise()不接受参数。不要试图向它传入回调或过滤器所有查询条件应通过链式方法.where()、.sort()、.limit()、.skip()等预先设定。返回值是标准 Promise可以放心使用await、.then()、.catch()、Promise.all()、Promise.race()以及 Bluebird 工具函数。务必成对处理成功与失败.then().catch()缺一不可用await时务必包裹try/catch。优先使用await在支持 Node.js v8 的现代环境下直接await query与.toPromise()在底层执行路径上等价但代码更简洁也更符合 docs/reference/waterline/queries/queries.md 的官方推荐。与其他查询方法组合.toPromise()应放在查询链的末尾即先.where()/.populate()/.sort()/.limit()等完成查询塑形最后再调用.toPromise()触发执行。九、延伸阅读docs/reference/waterline/queries/queries.md查询实例的完整介绍Deferred 语义、执行方式、回调/Promise 对比docs/reference/waterline/queries/exec.md.exec()回调风格的参数与示例docs/reference/waterline/queries/then.md.then()用法docs/reference/waterline/queries/catch.md.catch()与按错误类型过滤docs/reference/waterline/queries/where.md、docs/reference/waterline/queries/sort.md、docs/reference/waterline/queries/limit.md查询塑形方法docs/concepts/ORM/Querylanguage.md归一化查询语言docs/concepts/ORM/errors.mdORM 错误类型目录package.jsonparley 依赖声明parley: ^3.3.4lib/hooks/views/render.jsSails 内部使用 parley 的源码示例【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表