en un clic
node-best-practices
写 Node.js 后端时使用。异步、错误、依赖与安全的实战规范。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
写 Node.js 后端时使用。异步、错误、依赖与安全的实战规范。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | node-best-practices |
| description | 写 Node.js 后端时使用。异步、错误、依赖与安全的实战规范。 |
| category | language |
| tags | ["node","javascript","后端"] |
await、阻塞事件循环或配置硬编码时。async/await + try/catch;不混用回调,不漏 await 导致竞态规则: 所有异步操作统一用 async/await;禁止在 async 函数中混写 callback 风格;每个 await 表达式都必须被 try/catch 覆盖或由调用链上的统一错误处理捕获;await 不能遗漏——没有 await 的 Promise 是悬空的。
为什么: AI 生成 Node.js 代码时最常见的错误:router.get('/user', async (req, res) => { const user = getUser(req.params.id); res.json(user); })——忘记 await,user 是一个 Promise 对象而非数据,res.json 把 Promise 序列化成 {},请求返回空对象。这类 bug 在简单场景下测试不出来,到了有延迟的生产环境才暴露,且错误信息毫无指向性。
怎么做:
fs)→ 用 util.promisify 包一层,再 await。await Promise.all([...]) 而非串行多个 await。async 函数内若有 setTimeout/setInterval 回调,注意内部异常不会自动冒泡,需显式 try/catch。规则: 事件循环线程禁止执行耗时超过几毫秒的 CPU 密集操作(JSON 解析大文件、加密运算、图像处理、复杂正则);此类任务交给 worker_threads、独立进程或异步任务队列(BullMQ、Celery 等)处理。
为什么: Node.js 是单线程事件循环,一个同步计算如果耗时 200ms,这 200ms 内所有其他请求都被冻结。AI 常把 JSON.parse(fs.readFileSync('huge.json')) 或同步加密写在请求处理函数里——在压测前完全看不出问题,一旦数据量上去,P99 延迟暴涨,整个服务响应停滞。这是 Node.js 最致命的性能陷阱之一。
怎么做:
fs.createReadStream + JSONStream)或 Worker。worker_threads 或独立微服务。clinic.js 或 --prof 火焰图定位,不凭感觉优化。规则: Express/Koa 等框架中必须注册全局错误处理中间件(四参数 (err, req, res, next)),所有路由的异步错误通过 next(err) 或框架的 async wrapper 汇聚到这里;必须监听 process.on('unhandledRejection') 和 process.on('uncaughtException'),记录日志后优雅退出(不静默吞掉,也不忽视)。
为什么: AI 写 Express 时的典型遗漏:每个路由自己 catch 然后 res.status(500).json({error: e.message})——错误格式散落各处,有些路由根本没 catch,未处理的 rejection 让进程悄悄进入不一致状态继续服务请求。unhandledRejection 在 Node.js 15+ 默认会终止进程,但在旧版本只打印警告,AI 生成的代码常假设旧版行为。
怎么做:
asyncHandler 包装(自动把 rejection 转成 next(err));最后注册 app.use((err, req, res, next) => { ... }) 统一响应。unhandledRejection → 记录错误、触发优雅关闭(不 process.exit(1) 立即硬停,先排空连接池)。规则: 引入新依赖前评估:标准库或已有依赖能否满足需求?依赖须锁定精确版本(package-lock.json 或 yarn.lock 提交入库);定期运行 npm audit,高危漏洞须在 CI 中阻断;禁止为一个小功能引入体积巨大或维护不活跃的包。
为什么: AI 最容易犯的依赖滥用:为了格式化日期引入 moment(343KB,已停止维护),为了生成 UUID 引入一个包(原生 crypto.randomUUID() 已够用)。更危险的是引入被投毒或有供应链风险的包——npm audit 能发现已知 CVE,但不运行就是零防护。package-lock.json 不提交 → 不同环境安装不同版本 → "在我机器上好好的"。
怎么做:
npm ci(不是 npm install,严格遵守 lockfile)+ npm audit --audit-level=high 失败即阻断。crypto、path、stream、url)解决的,不依赖第三方。helmet)默认开规则: 所有环境相关配置(数据库连接串、密钥、端口、Feature Flag)通过环境变量注入,禁止硬编码在代码或 git 追踪的配置文件中;所有外部输入(HTTP body、query、header)必须通过 schema 校验(Zod/Joi);HTTP 服务默认启用 helmet。
为什么: AI 经常在示例代码里直接写 const DB_URL = "mongodb://admin:password@localhost",用户照抄提交入库,密钥泄漏到 GitHub 是 Node.js 生态里最高频的安全事故之一。输入校验缺失则是 NoSQL 注入、原型链污染等攻击的入口——req.body 是完全不可信的用户输入,AI 却经常直接 spread 进数据库查询。
怎么做:
dotenv 加载 .env(.env 加入 .gitignore);生产环境走 Secret Manager/Vault;用 Zod z.object({DB_URL: z.string().url()}) 在启动时验证必要环境变量,缺失则启动失败而非运行时崩溃。const body = UserSchema.parse(req.body),校验失败抛出错误,由全局中间件返回 400。app.use(helmet()) 写在所有路由之前,默认禁止 X-Powered-By、开启 Content-Security-Policy 等。await + 无全局错误处理 + 硬编码密钥// 反例 — 漏 await、密钥硬编码、错误处理散乱
const express = require('express');
const app = express();
const SECRET = "super_secret_key_123"; // ❌ 硬编码密钥
app.get('/user/:id', (req, res) => { // ❌ 非 async,或 async 但没 try/catch
const user = User.findById(req.params.id); // ❌ 漏 await,user 是 Promise
res.json(user); // ❌ 返回 {}
});
// ❌ 没有全局错误处理中间件
// 正例 — async/await 正确,统一错误处理,配置走环境变量
import express from 'express';
import helmet from 'helmet';
import { z } from 'zod';
import { asyncHandler } from './middleware/asyncHandler.js';
const app = express();
app.use(helmet()); // ✅ 安全头默认开
app.use(express.json());
const ParamSchema = z.object({ id: z.string().uuid() });
app.get('/user/:id', asyncHandler(async (req, res) => {
const { id } = ParamSchema.parse(req.params); // ✅ 输入校验
const user = await User.findById(id); // ✅ 正确 await
if (!user) return res.status(404).json({ error: '用户不存在' });
res.json(user);
}));
// ✅ 全局错误处理中间件
app.use((err, req, res, next) => {
if (err instanceof z.ZodError) return res.status(400).json({ error: err.issues });
console.error(err);
res.status(500).json({ error: '服务器内部错误' });
});
// 反例 — 同步读取大 JSON,冻结事件循环
app.post('/import', (req, res) => {
const data = JSON.parse(
fs.readFileSync('/uploads/huge.json', 'utf8') // ❌ 同步 I/O,阻塞所有请求
);
process(data);
res.json({ ok: true });
});
// 正例 — 流式解析,不阻塞事件循环
import { createReadStream } from 'fs';
import JSONStream from 'jsonstream';
import { pipeline } from 'stream/promises';
app.post('/import', asyncHandler(async (req, res) => {
const results = [];
await pipeline(
createReadStream('/uploads/huge.json'), // ✅ 流式读取
JSONStream.parse('*'),
async function* (source) {
for await (const item of source) {
results.push(await processItem(item)); // ✅ 逐条处理,不积压内存
}
}
);
res.json({ count: results.length });
}));
async 函数中,每个返回 Promise 的调用前都有 await?process.on('unhandledRejection') 并做了日志记录 + 优雅退出?helmet() 是否在所有路由之前挂载,npm audit 在 CI 中有没有对高危漏洞阻断?