Sails.js 的 `.fetch()` 查询方法:让 Waterline 回传增删改记录的完整指南
2026/9/21 23:19:20 网站建设 项目流程

Sails.js 的.fetch()查询方法:让 Waterline 回传增删改记录的完整指南

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

.fetch()是 Sails.js 内置 ORM(Waterline)中一个短小但极为实用的查询实例(query instance)方法。执行.create().createEach().update().destroy()这类写操作时,默认情况下数据库不会回传受影响的记录;链式调用.fetch()即可让 Waterline 与底层数据库适配器把这些记录原样返回。读完本文,你将掌握.fetch()的用法、返回类型差异、与.meta({fetch: true})的关系,以及在大批量写操作下如何规避性能陷阱。

为什么需要.fetch():写操作的默认"沉默"行为

在 Sails 应用中,查询实例(query instance)是模型方法返回的可链式延迟对象(Deferred),例如Zookeeper.find()返回的对象在await之前并不会真正执行(详见 Queries 文档)。对于读操作(.find()等),执行后自然能拿到记录;但对于写操作,情况不同:

  • 执行.update().create().createEach().destroy()时,默认不会返回任何数据;
  • 如果使用回调风格(.exec()),则回调的第二个参数将是undefined

这一设计是为了性能:写操作默认只关心"是否成功",数据库无需把受影响的记录序列化回传。当你确实需要拿到"刚刚写入/更新/删除的那条(些)记录"时,才需要链式调用.fetch(),显式要求 Waterline 和底层数据库适配器把结果送回来。

这一默认行为在框架源码中也有直接印证。在 lib/hooks/blueprints/actions/create.js 中,blueprint 的 create action 执行完Model.create(data).meta(queryOptions.meta).exec(...)后,会检查:

// If we didn't fetch the new instance, just return 'OK'. if (!newInstance) { return res.ok(); }

也就是说,未启用 fetch 时,即便通过 REST 接口调用 blueprint create,响应也只是状态 OK,而不携带新记录数据。

基本用法:零参数,一行链式调用

.fetch()

.fetch()不接受任何参数。它必须链式接在写操作模型方法返回的查询实例之后,并且需要在查询真正执行(await.exec().then().toPromise())之前调用。

典型示例:创建后立即取回新记录

原文档给出的经典示例是在.create()之后链式调用.fetch(),从而立刻拿到带主键(id)的新记录:

var newUser = await User.create({ fullName: 'Alice McBailey' }).fetch(); sails.log(`Hi, ${newUser.fullName}! Your id is ${newUser.id}.`);

执行await后,newUser即为数据库中实际写入的那条记录(包含自动生成的主键、默认值、生命周期回调写入的字段等)。没有.fetch()的话,newUser将是undefined

各写操作方法的返回类型与差异

.fetch()在不同写操作方法上的返回形态不同,理解这一点可以避免解包时的低级错误:

模型方法默认返回链式.fetch()后的返回
.create()单个记录(((dictionary))),即新建的那条记录
.createEach()记录数组(((array))),包含所有新建记录
.update()记录数组(((array))),包含所有被更新的记录
.destroy()记录数组(((array))),包含所有被删除的记录

以 .create() 文档 中的 Result 表格说明为例:为了优化性能,创建出的记录默认不作为结果返回;只有链式调用.fetch()后,新记录才会被回传——并且要注意,在部分适配器中这需要额外执行一次数据库查询.update().destroy()的文档也做了同样说明。

批量更新后取回所有受影响记录

var updatedUsers = await User.update({name:'Finn'}) .set({ name:'Jake' }) .fetch(); sails.log(`Updated all ${updatedUsers.length} user${updatedUsers.length===1?'':'s'} named "Finn" to have the name "Jake". Here they are now:`); sails.log(updatedUsers);

注意.update()的返回始终是数组——即使只有一条记录被更新。同时需注意:update查询不支持skip/limit分页与select投影。

删除后取回被销毁的记录

var burnedBooks = await Book.destroy({ controversiality: { '>': 0.9 } }).fetch(); sails.log('Deleted books:', burnedBooks);

同样,.destroy()返回被删除记录的数组。另请留意 .destroy() 文档 中的安全提示:如果 criteria 传入空字典{},将销毁表中所有记录。

新记录的主键是.fetch()最常见的用途

.create()后拿不到主键是新手最常见的困惑之一。由于主键通常由数据库自动生成(自增 id 或 uuid),没有.fetch()就无法在后续逻辑中引用新记录。这也是.fetch()在 Sails 应用中出现频率最高的场景。

底层原理:.fetch()就是.meta({fetch: true})的快捷方式

原文档明确给出了关键结论:.fetch()只是.meta({fetch: true})的语法糖(shortcut)

.meta() 文档 的 Supported options 表格完整列出了fetch这一 meta 键:

OptionTypeDefaultDetails
fetch((boolean))false在执行.update().create().createEach().destroy()查询时,设为true可让数据库适配器回传所有被更新/删除的记录;否则.exec()回调的第二个参数为undefined。警告:对影响大量记录的 update/destroy 查询启用该键可能引发性能问题。

因此,下面两种写法完全等价:

var newUser = await User.create({ name: 'alice' }).fetch(); // 等价写法: var newUser2 = await User.create({ name: 'alice' }).meta({ fetch: true });

meta 键还有第三种设置途径:在显式回调之后传入字典,例如:

User.create({ name: 'alice' }, function(err, newUser){/* ... */}, { fetch: true });

从框架源码看,fetch键贯穿了整个 blueprint 层。例如 lib/hooks/blueprints/parse-blueprint-options.js 在为 create action 组装查询选项时直接设置了queryOptions.meta = { fetch: true };同一文件 L344-L345 对 update action 也做了同样处理。这说明:REST 蓝图接口默认就能回传新建/更新后的记录(随后还会在 create.js 中通过findOne做一次 populate 再返回)。

.fetch()与生命周期回调(lifecycle callbacks)的联动

fetch键不仅影响返回值,还影响模型生命周期回调的执行。在 lib/hooks/blueprints/actions/destroy.js 的源码注释中明确写道:

...we'll also include the meta query options for the purpose of enabling theafterDestroylifecycle callback (which only runs if.meta({fetch: true})is included).

也就是说,blueprint destroy action 之所以在findOnedestroy上都透传queryOptions.meta,正是为了让afterDestroy这类生命周期回调在fetch: true时得以触发。由此可以推断:当你需要依赖写操作后的生命周期回调(如afterCreateafterUpdateafterDestroy)时,带上.fetch()(或meta: {fetch: true})是必要条件之一;反之,若确认不需要回传数据,省略它还能顺带跳过这些回调的额外开销。

性能警告:大批量写操作慎用.fetch()

原文档给出了一条明确的警告:

Warning: This is not recommended for update/destroy queries that affect large numbers of records.

原因在于:要求适配器收集并回传每一条受影响记录的完整数据,会显著增加数据库与 Node.js 进程之间的数据传输量,部分适配器还需要额外发起一次查询(见 .create() 文档 中 "requires an extra database query in some adapters" 的说明)。在批量任务(例如定时清理、批量状态迁移)中,这可能会拖慢整次操作。

实践建议:

  • 只需确认"是否成功":省略.fetch(),仅关注是否抛错;
  • 需要主键或受影响记录:使用.fetch(),但尽量缩小 criteria 的影响范围;
  • 大表全量更新/删除:考虑分批处理(如按id分段),避免单次查询影响海量记录;
  • 追求性能且数据库支持:把级联删除等需求下推到物理层(如 MySQL/PostgreSQL 的 CASCADE 约束),而不是依赖 ORM 层逐条处理(详见 .meta() 文档 中关于cascade键的说明)。

错误处理与执行方式

.fetch()本身不引入新的错误类型;它与普通查询一样,错误取决于所执行的模型方法与底层适配器(UsageErrorAdapterErrorError等,详见各模型方法文档中的 Errors 小节)。查询既可以用await执行(推荐,代码更简洁、更安全),也可以用.exec()回调或.then()/.catch()链式执行。需要提醒的是:.exec()回调风格下,.fetch()决定的就是回调第二参数是否为undefined

小结

.fetch()是 Waterline 查询实例中"用一行代码换回写操作结果"的便捷方法:

  • 适用对象:.create().createEach().update().destroy()
  • 返回值:单个记录或记录数组,取决于模型方法;
  • 本质:.meta({fetch: true})的快捷方式,默认fetch: false
  • 附加价值:与afterCreate/afterUpdate/afterDestroy等生命周期回调联动(blueprint destroy 源码中有明确印证);
  • 主要风险:影响大量记录的 update/destroy 查询会带来性能开销,应谨慎使用。

在 fetch.md 原文档 基础上,建议进一步阅读 Queries 总览 理解查询实例的 Deferred 机制,以及 .meta() 了解cascadeskipAllLifecycleCallbacksdecrypt等更多 meta 键的完整能力。

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询