ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Sails Helpers 完整实战指南:在 Node.js MVC 框架中复用代码、自动校验输入与声明式错误处理

Sails Helpers 完整实战指南:在 Node.js MVC 框架中复用代码、自动校验输入与声明式错误处理 后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载Sails 从 v1.0 起内置了对helpers辅助方法的原生支持——这是一种把重复 Node.js 代码抽离为独立文件、并在多个位置复用的标准方案。本文以 Sails 官方文档中关于 Helpers 的核心概念为主线结合本仓库的 helpers 钩子源码lib/hooks/helpers/index.js、lib/hooks/helpers/private/load-helpers.js与集成测试test/integration/hook.helpers.test.js深入讲解其定义规范、调用方式、异常处理与组织策略。读完本文你将能够编写自校验、自文档化的可复用 helper并在 action、自定义响应、命令行脚本、单元测试乃至其他 helper 中安全高效地调用它们。为什么需要 Helpers在开发基于 Sails 的应用时你常常会在多个 action 中编写几乎相同的代码——例如重复的数据库查询、重复的字符串处理、重复的鉴权逻辑。这种复制粘贴既容易引入 bug也让维护变得痛苦某处逻辑需要调整时你不得不同步修改多处。Helpers 正是为解决这一问题而生的推荐方案把重复代码抽取到单独的文件中然后在各处复用。官方文档明确列出了 helpers 的典型使用场景Actions 与 Controllers自定义响应命令行脚本Sails 通过 Whelk 提供sails run脚本能力参见 Shell 脚本单元测试其他 helpers一个最直观的例子在 action 中把重复代码替换为对自定义 helper 的一次调用const greeting await sails.helpers.formatWelcomeMessage(Bubba); sails.log(greeting); // Hello, Bubba!只要代码所在位置能够访问到sails应用实例helper 就可以被调用——这意味着应用启动之后的大部分代码路径都满足条件。如何定义一个 HelperSails 中的每个 helper 就是一个位于api/helpers/目录下的 CommonJS 模块。以下是一个简单但规范完整的 helper 定义// api/helpers/format-welcome-message.js module.exports { friendlyName: Format welcome message, description: Return a personalized greeting based on the provided name., inputs: { name: { type: string, example: Ami, description: The name of the person to greet., required: true } }, fn: async function (inputs, exits) { const result Hello, ${inputs.name}!; return exits.success(result); } };这个文件虽然简单却体现了一个优秀 helper 的几大特征友好的名称与描述friendlyName、description让代码读者一眼就能明白这个工具是干什么的声明式 inputs清楚描述该工具接收哪些参数便于理解如何调用单一职责以最简单的方式完成一个离散的任务。这种机器规范machine specification你并不陌生——Sails 的 Shell 脚本 与 actions2 风格的 action 遵循的是同一套规范因此它们之间的知识可以互相迁移。fn函数helper 的核心fn是 helper 真正执行逻辑的地方它接收两个参数inputs输入值的字典即实参/arginshelper 用它来获得调用者传入的数据exits回调函数字典helper 用它把控制权交还给调用方。需要特别注意的是与普通 JavaScript 函数用return返回结果不同helper 是通过把结果值传入exits.success(...)来返回的。fn: async function (inputs, exits) { const result Hello, ${inputs.name}!; return exits.success(result); }Inputs自校验的函数参数helper 声明的inputs类似于普通 JavaScript 函数的参数——它们定义了代码要处理的数据。但关键区别在于inputs 会被自动校验。如果调用时传入的实参类型与声明不匹配或缺少某个required: true的输入helper 会直接触发错误。因此helpers 是**自校验self-validating**的。每个 input 定义至少包含一个type属性Sails 支持以下输入类型与模型属性定义中的类型语义一致类型说明string字符串值number数值整数和浮点数均可booleantrue或falserefJavaScript 变量引用可以是任意值字典、数组、函数、流等在此基础上你还可以为输入配置defaultsTo默认值required: true必填allowNull允许null以及几乎所有更高级的校验规则例如isEmail。调用 helper 时传入的实参按声明顺序对应 inputs 的键顺序如果你更愿意按名称传参可以链式使用.with()const greeting await sails.helpers.formatWelcomeMessage.with({ name: Bubba });Exits声明所有可能的结局Exits 描述了 helper 所有可能的结果——无论好坏。每个 helper自动支持error和success两个出口当fn触发success时helper 正常返回当fn触发success以外的其他出口时helper 会抛出一个 Error除非调用方使用了.tolerate()。你还可以暴露额外的自定义出口称为异常/exceptions让调用方代码能够针对性地处理特定例外场景。这保证了代码的透明度和可维护性——声明和协商错误变得轻松简单。自定义出口定义在exits字典中。好的实践是为每个自定义异常提供显式的description属性这样 Sails 在必要时可以用它自动构造合适的 JavaScript Error 实例针对非success出口。假设有一个名为inviteNewUser的 helper暴露了一个emailAddressInUse自定义出口。当传入的邮箱已存在时fn触发该出口从而让调用方无需污染结果值、也无需手写大量try/catch就能处理这一具体场景。例如在自带badRequest出口的 action 中调用该 helperconst newUserId sails.helpers.inviteNewUser(bubbahawtmail.com) .intercept(emailAddressInUse, badRequest);上面这行漂亮的简写等价于.intercept(emailAddressInUse, (err){ return badRequest; });而.intercept()本身也只是另一个快捷方式让你不必每次手动编写try/catch来协商这些错误。在 helper 内部fn负责触发其中一个出口——既可以通过抛出特殊的exit signal也可以通过调用出口回调例如exits.success(foo)。如果 helper 通过 success 出口返回了结果例如foo该值就会成为 helper 的返回值。同步 Helpersync默认情况下所有 helper 都被视为异步的。这是一个安全的默认假设但并不总是事实。当你确定某个 helper 是同步的时可以通过设置sync: true来告诉 Sails从而允许调用方不使用await直接调用以优化性能// api/helpers/foo-bar.js module.exports { sync: true, fn: function (inputs, exits) { // 注意不再是 async function return exits.success(...); } };重要提醒不带await调用一个异步 helper 是不会生效的。如果你把sync设为true务必把fn: async function改成fn: function在 Helper 中访问req如果你要设计一个专门从 action 中解析请求头的 helper可以利用请求对象req上现成的方法和属性。让 action 中的代码把req传给 helper 的最简单方式就是定义一个type: ref的输入inputs: { req: { type: ref, description: The current incoming request (req)., required: true } }然后在 action 中这样使用const headers await sails.helpers.parseMyHeaders(req);生成一个 HelperSails 提供了内置生成器可以自动创建新的 helpersails generate helper foo-bar该命令会创建文件api/helpers/foo-bar.js在代码中可通过sails.helpers.fooBar访问。初始生成的 helper 是一个没有任何 inputs、只有默认出口success和error的通用模板执行时立即触发success出口。如何调用一个 Helper每当 Sails 应用加载时它会找到api/helpers/目录下的所有文件将其编译为可调用函数并以**文件名的驼峰命名camelCase**作为键存入sails.helpers字典。之后任何 helper 都可以通过在代码中带上await并传入实参来调用const result await sails.helpers.formatWelcomeMessage(Dolly); sails.log(Ok it worked! The result is:, result);这种用法与你熟悉的模型方法如.create()大致相同。.timeout(ms)调用超时.timeout()方法为 helper 的执行设置最大等待毫秒数。如果执行超过指定时间会抛出TimeoutError// 如果 helper 耗时超过 5 秒则抛出 TimeoutError var result await sails.helpers.someLongRunningTask() .timeout(5000);传入0可以禁用 helper 实现中可能已设置的所有超时。.retry(negotiationRule, retryDelaySeries)指数退避重试.retry()方法为 helper 调用附加一个指数退避重试策略当 helper 失败时会在延迟后自动重试。参数类型说明negotiationRuleString、Object 或 Array可选指定哪些错误应触发重试。可以是错误码字符串如TimeoutError、字典如{code: E_TIMEOUT}或规则数组。省略时对所有错误重试。retryDelaySeriesNumber 数组可选每次重试尝试之间等待的毫秒数数组。数组长度决定重试次数。默认为[250, 500, 1000]3 次重试延迟递增。// 使用默认延迟250ms、500ms、1000ms最多重试 3 次 var result await sails.helpers.riskyOperation() .retry(); // 仅在 TimeoutError 时重试 var result await sails.helpers.externalApiCall() .retry(TimeoutError); // 自定义指数退避4 次重试1s、2s、4s、8s var result await sails.helpers.flakyService() .retry(TimeoutError, [1000, 2000, 4000, 8000]);链式组合.timeout()与.retry()这两个方法可以相互链式组合也可以与.intercept()、.tolerate()等其他 helper 修饰符一起链式使用var data await sails.helpers.externalApiCall(apiPayload) .timeout(10000) .retry(TimeoutError, [1000, 2000, 5000]) .intercept(serviceUnavailable, backboneError) .intercept((err) { sails.log.error(API call failed:, err); return err; });注意当.timeout()与.retry()组合使用时超时作用于每一次单独的尝试而不是所有重试的总耗时。同步调用如果 helper 声明了sync属性你也可以不用await直接调用const greeting sails.helpers.formatWelcomeMessage(Timothy);但在移除await之前请确认该 helper 确实是同步的——没有await的异步 helper 永远不会执行组织 Helper子目录分组当应用的 helper 很多时把相关 helper 分到子目录会更有条理。例如假设有一批userhelper 和若干itemhelper目录结构如下api/ helpers/ user/ find-by-username.js toggle-admin-role.js validate-username.js item/ set-price.js apply-coupon.js调用时每个子文件夹名如user、item会成为sails.helpers对象中额外的一层属性。于是你可以用sails.helpers.user.findByUsername()调用find-by-username.js用sails.helpers.item.setPrice()调用set-price.js。从源码看这套机制的实现位于 lib/hooks/helpers/index.js 的furnishPack与furnishHelper方法helper 定义会先经过 kebab-case 归一化再按点分路径逐层构建pack中间层容器与最终的 callable。值得注意的是源码中针对嵌套超过一层的 helper 会打印 verbose 提示——尽量保持目录扁平用更明确的长文件名代替过深的目录层级往往更利于维护和调用。异常处理与自动出口转发你可能习惯用设置错误码再检查错误的方式做精细化错误处理。这种方案可行但费时且难以追踪。在 Sails helpers 中有几种更便捷的错误处理方式详见.tolerate().intercept()特殊 exit signals参见 ActionsAndControllers自动出口转发automatic exit forwarding是另一个关键特性调用方代码可以按需、逐例选择接入尽可能少或尽可能多的自定义出口。换句话说调用 helper 时完全忽略它的自定义notUnique出口也没关系——你的代码因此保持简洁直观而将来需求变化时你随时可以回来补充对该自定义出口的处理。源码视角Helpers 钩子如何工作深入本仓库源码可以更好地理解 helpers 的加载与构建机制。加载过程应用启动时lib/hooks/helpers/index.js 中定义的 helpers 钩子会在initialize阶段调用 lib/hooks/helpers/private/load-helpers.js通过includeAll.optional()扫描sails.config.paths.helpers指向的目录默认api/helpers/正则过滤掉.md/.txt等非定义文件若配置了sails.config.helpers.moduleDefinitions实验特性用于以编程方式注入 helper 字典会浅合并到磁盘加载结果之上对每个定义将文件路径的各段做 camelCase 处理得到 key path例如user-helpers/foo/my-helper→userHelpers.foo.myHelper并把文件名作为强制identity调用furnishHelper把定义构建为wet machine可调用对象挂载到sails.helpers上任何构建失败都会以E_FAILED_TO_BUILD_CALLABLE错误码终止应用加载。另外源码还会检查 helper 定义中是否混入了 action 专属的属性如files、responseType、viewTemplatePath、statusCode一旦发现便打印警告——这些能力只能用于 actionhelper 不可使用。配置项sails.config.helpers.usageOpts钩子的defaults暴露了 helpers 的调用风格配置lib/hooks/helpers/index.jsdefaults: { helpers: { usageOpts: { arginStyle: serial, // serial 按顺序传参 | named 以字典传参 execStyle: natural // natural/immediate 立即执行 | deferred 延迟执行需 .now() 触发 }, moduleDefinitions: undefined } }arginStyle: serial对应sails.helpers.foo(a, b)这种按声明顺序传参的写法named则对应sails.helpers.foo({...})。execStyle: natural表示同步 helper 调用后立即执行deferred则返回一个待执行的 deferred 对象需要调用.now()或.execSync()才会真正运行。钩子在configure阶段会做一次向后兼容检测如果应用package.json中的sails依赖指向1.0.0-44之前的预发布版本会自动把usageOpts切换为{arginStyle: named, execStyle: deferred}以保持旧行为并在存在 helper 时打印升级提示。自 v1.0.0-44 起默认风格变为 serial natural但.with({...})随时可以把调用切换为按名称传参。测试验证集成测试 test/integration/hook.helpers.test.js 验证了上述机制从磁盘加载api/helpers/greet.js并与通过helpers.moduleDefinitions编程式注入的ucasehelper 合并断言sailsApp.helpers中共有 2 个 helper每个 helper 都是可调用函数且都带有.with方法开箱即用地支持serialnatural风格与.with()命名传参且两种方式结果一致支持.customize({arginStyle, execStyle})在调用层面临时切换风格。实战示例封装数据库查询的getRecentUsers官方文档docs/concepts/Helpers/ExampleHelper.md给出了一个极具代表性的实践案例——把重复的数据库查询封装成 helper。假设应用的User模型有lastActiveAt字段记录最近登录时间我们需要反复查询最近在线的用户列表可以写成// api/helpers/get-recent-users.js module.exports { friendlyName: Get recent users, description: Retrieve a list of users who were online most recently., extendedDescription: Use activeSince to only retrieve users who logged in since a certain date/time., inputs: { numUsers: { friendlyName: Number of users, description: The maximum number of users to retrieve., type: number, defaultsTo: 5 }, activeSince: { description: Cut-off time to look for logins after, expressed as a JS timestamp., extendedDescription: Remember: A _JS timestamp_ is the number of **milliseconds** since [that fateful night in 1970](https://en.wikipedia.org/wiki/Unix_time)., type: number, defaultsTo: 0 } }, exits: { success: { outputFriendlyName: Recent users, outputDescription: An array of users who recently logged in., }, noUsersFound: { description: Could not find any users who logged in during the specified time frame. } }, fn: async function (inputs, exits) { // 执行查询 var users await User.find({ active: true, lastLogin: { : inputs.activeSince } }) .sort(lastLogin DESC) .limit(inputs.numUsers); // 如果没有找到用户触发 noUsersFound 出口 if (users.length 0) { throw noUsersFound; } // 否则通过 success 出口返回记录 return exits.success(users); } };调用方式在 action 等应用代码中使用默认选项调用var users await sails.helpers.getRecentUsers();传入参数以修改查询条件var users await sails.helpers.getRecentUsers(50);或者获取自 2017 年圣帕特里克节以来登录的最近 10 位用户await sails.helpers.getRecentUsers(10, (new Date(2017-03-17)).getTime());这些在运行时传入 helper 的值有时被称为argins或 options它们与 helper 声明的 input 定义的键顺序如numUsers、activeSince一一对应。同样可以链式调用.with()使用命名参数await sails.helpers.getRecentUsers.with({ numUsers: 10, activeSince: (new Date(2017-03-17)).getTime() });处理noUsersFound异常要显式处理noUsersFound出口而不是简单地把它当成一般错误可以使用.tolerate()或.intercept()var users await sails.helpers.getRecentUsers(10) .tolerate(noUsersFound, (){ // ... 处理未找到用户的情况。例如 sails.log.verbose( Worth noting: Just handled a request for active users during a time frame where no users were found. Anyway, I didn\t think this was possible, because our app is so cool and popular. But there you have it. ); });var users await sails.helpers.getRecentUsers(10) .intercept(noUsersFound, (){ return new Error(Inconceivably, no active users were found for that timeframe.); });这个例子的启示使用 helpers 的最大优势在于只需修改一处代码就能更新应用许多地方的功能。例如把numUsers的默认值从5改成15所有使用该 helper 的位置返回的默认列表大小就都更新了。同时得益于numUsers、activeSince这样定义良好的 inputs一旦不小心传入了非法非数值值你会立刻得到有帮助的错误提示。几点补充说明description、friendlyName等字段并非严格必需但它们在保持代码可维护性上价值巨大——尤其是当 helper 要在多个应用间共享时noUsersFound出口是否必要取决于你的应用如果你在无用户时总要执行特定动作例如重定向到其他页面这个出口就很有价值反之如果你只是根据是否有用户来微调视图中的文案那么让success出口返回数组、在 action 或视图代码里检查length可能更合适。进阶主题与下一步与 actions2、shell scripts 的统一规范helper 遵循与 Shell 脚本、actions2 相同的机器规范同一套inputs/exits/fn心智模型可以复用。与其他模块协同helper 可以调用模型方法也可以调用其他 helper在自定义响应与单元测试中同样可以使用。sails-hook-organics在Web App模板中捆绑的sails-hook-organics提供了大量免费、开源、MIT 许可的常用 helper可直接借鉴其定义风格。源码研读路径若想进一步深入可以从 lib/hooks/helpers/index.js钩子入口与usageOpts配置、lib/hooks/helpers/private/load-helpers.js磁盘加载与构建以及 test/integration/hook.helpers.test.js行为验证入手machine与parley依赖见 package.json则提供了底层的 callable 构建与 Promise 化能力。总结Helpers 是 Sails v1.0 应用保持可维护性的核心工具。通过声明式输入 自动校验 命名出口 声明式错误协商这一套机制你既能消灭重复代码又能获得自文档化、自校验的高质量代码单元。从简单的formatWelcomeMessage到封装数据库查询的getRecentUsers掌握这套模式后你就能在自己的 Sails 应用中流畅地设计、生成、组织和调用 helpers 了。赞分享后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载相关推荐零代码构建安全APIDrogon框架参数校验与错误处理实战指南零代码构建安全APIDrogon框架参数校验与错误处理实战指南 Drogon是一款基于C14/17的高性能Web应用框架它提供了简洁高效的API开发方式后端Web框架Sails Helpers 实战指南从 getRecentUsers() 示例掌握可复用代码封装Sails Helpers 实战指南从 getRecentUsers 示例掌握可复用代码封装 导读 本篇以 Sails 官方示例 getRecentUsers后端告别重复HTTP代码Forest声明式客户端框架实战指南告别重复HTTP代码Forest声明式客户端框架实战指南 为什么选择声明式HTTP客户端 你是否还在为这些问题烦恼 每次调用第三方API都要编写大量重复的后端上一篇dotnet9x日志系统Windows 95环境下的日志记录方案下一篇永不离线IoT设备断线重连的实战策略与最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表