在 N8N大学 (n8ndx.com) 摸爬滚打这么多年,笔者见过太多同学在 Function 节点里栽跟头。明明是自己熟悉的 JavaScript,写在 n8n 里却不是报 ReferenceError 就是卡在 JSON 解析上。
这感觉就像你明明会开车,却被扔进了一台没有仪表盘的赛车里——技术是通的,但规则变了。今天,笔者就带你拆解 n8n Function 节点中最容易踩的 3 个坑,并给出直接能用的解决思路。
坑一:数据进出的“次元壁”——JSON 序列化与反序列化
这是新手最容易遇到的第一个报错:Cannot read property '0' of undefined 或者在输出里看到 [object Object]。
为什么会出现这个坑?
n8n 的数据流在节点之间传递时,本质上是 JSON 格式的对象。但在 Function 节点内部,你拿到的 items 是 JavaScript 对象。当你通过 $json 访问数据时,n8n 会自动处理。但当你试图直接操作 items[0].json 之外的属性,或者手动构建新数据时,如果忘记把 JS 对象转回 JSON 格式,数据传到下一个节点就会“变质”。
解决思路:
记住一个铁律:在 Function 节点内部,数据结构是 items 数组,每个 item 包含 json、binary 等属性。输出时,必须显式地把数据塞进 items。
下面是一个标准的“数据重塑”代码片段:
// 正确的写法:构建一个新的 item 数组
const newItems = [];
for (const item of $input.all()) {
const currentData = item.json;
// 处理逻辑...
const processedData = {
userId: currentData.id,
name: currentData.name.toUpperCase(),
processedAt: new Date().toISOString()
};
// 关键点:必须将数据赋值给 item.json
// 如果需要新增字段,直接在 processedData 里定义
newItems.push({
json: processedData
});
}
return newItems;
避坑指南: 如果你只是修改现有字段,可以直接修改 item.json 并返回 $input.all()。但如果是新增字段或完全重构数据结构,务必手动实例化 { json: { ... } } 对象。
坑二:作用域的“迷魂阵”——$input 与 $json 的爱恨情仇
很多从 n8n 旧版本迁移过来的用户,或者习惯了其他编程环境的开发者,经常在这个坑里打转。报错通常是 ReferenceError: $input is not defined 或者数据取出来全是 undefined。
为什么会这样?
n8n 提供了一些特殊的“魔法变量”(Magic Variables),比如 $input、$json、$now 等。这些变量只在特定的上下文环境中有效。
- $json: 通常代表当前节点的第一条输入数据(item)。在简单的数据映射中很好用,但在处理多条数据时容易混淆。
- $input: 这是一个强大的对象,包含
first(),all(),last()等方法。它是处理批量数据的首选。
解决思路:
笔者的建议是:**除非你明确知道只需要处理第一条数据,否则永远优先使用 $input.all()**。
看一个对比案例:
// ❌ 错误示范:在批量数据输入时,$json 可能只取到第一条,
// 导致后续数据丢失或逻辑错误。
const data = $json;
// ✅ 正确示范:遍历所有输入的 items
const results = [];
const allInputs = $input.all();
for (const item of allInputs) {
// 这里的 item.json 才是当前循环中安全的数据源
const value = item.json.fieldName;
results.push({ json: { result: value } });
}
return results;
此外,N8N大学 提醒大家:在 Function 节点里,你不能直接访问上一个节点的输出变量名(比如 {{ $node["Set"].json["myVar"] }}),这在 Function 节点内部是不生效的。必须通过 $input 对象来获取上游数据。
坑三:异步与循环的“性能黑洞”——阻塞与内存溢出
这个坑比较隐晦,通常发生在数据量稍大(比如几百条以上)或者需要调用外部 API 时。你可能发现 n8n 运行变慢,甚至直接卡死,控制台报错 JavaScript heap out of memory。
为什么会这样?
Function 节点默认是同步执行的。如果你在一个 for 循环里做耗时操作(比如 HTTP 请求),n8n 会等待所有循环结束才释放资源。如果数据量大,内存占用会飙升。
更糟糕的是,很多同学喜欢用 await 但写错了位置,或者试图在 Function 节点里处理复杂的递归,这在 n8n 的沙箱环境中是非常危险的。
解决思路:
1. **善用 n8n 原生节点代替 JS 循环**:如果只是简单的数据转换,尽量使用 Set 节点或 Spreadsheet File 节点,它们比 JS 循环更高效。
2. **如果必须用 JS,请使用 Promise.allSettled**:如果你需要在 Function 节点并发请求外部 API,不要用简单的 for...of 串行执行。
// 优化前:串行执行,速度慢
for (const item of $input.all()) {
const res = await $http.request({ method: 'GET', url: item.json.url });
// ...处理
}
// 优化后:并发执行,效率高
const allInputs = $input.all();
const promises = allInputs.map(item => {
return $http.request({ method: 'GET', url: item.json.url })
.then(response => ({ json: { ...item.json, response: response.data } }))
.catch(error => ({ json: { ...item.json, error: error.message } }));
});
// 等待所有请求完成(无论成功失败)
return await Promise.allSettled(promises);
预防措施: 当数据量超过 1000 条时,尽量避免在单个 Function 节点内完成所有逻辑。考虑使用 Split In Batches 节点将任务拆分,这是 n8n 处理大批量数据的黄金法则。
FAQ 问答
Q1: 在 Function 节点里可以使用第三方库(如 lodash 或 moment)吗?
A: 默认情况下,Function 节点运行在沙箱环境中,无法直接 require 外部模块。如果你想使用第三方库,需要部署自定义的 n8n 实例,并修改配置文件允许加载外部模块,或者使用 Code 节点(新版 n8n 推荐)并安装依赖。对于大多数场景,原生 ES6+ 语法已经足够强大。
Q2: 为什么我的 console.log 在日志里看不到?
A: n8n 的 Function 节点日志输出有时比较隐蔽。确保你在 Workflow 画布上点击了“Execution”查看详细日志。如果是在云端版本,日志级别可能受限。建议使用 return [{ json: { debug: variable } }] 的方式将变量输出到下个节点,这样能直观看到数据变化。
Q3: 如何处理包含嵌套 JSON 的数据?
A: 访问嵌套数据时,务必做好防御性编程。例如 const city = item.json?.address?.city || 'Unknown'。使用可选链操作符 ?. 可以有效避免 TypeError: Cannot read property 'xxx' of null。
总结与资源
Function 节点是 n8n 的瑞士军刀,但刀刃锋利,容易伤手。掌握 JSON 数据结构、理解 $input 作用域、并警惕 异步性能陷阱,是进阶 n8n 高手的必修课。
如果你在 n8n 开发中遇到更多棘手问题,欢迎访问 N8N大学 (n8ndx.com) 获取更多实战教程。记住,最好的自动化不是最复杂的代码,而是最稳定、最易维护的逻辑。