DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关

Deno 2.8.0 用 map(unlink) 报 callback 必须是函数

Deno 2.8.0 的 node:fs Promise 包装可能把 map 的索引传给底层 callback 参数。显式传路径或采用 2.8.1 修正,并核对部分删除和真实文件错误。

把原来能在 Node 中运行的清理代码交给 Deno 2.8.0,Promise.all 还没完成就报错:

TypeError: The "callback" argument must be of type function. Received type number (0)

如果调用的是 node:fs 的 Promise 接口,而代码写成 paths.map(fs.promises.unlink),先检查兼容层是否把数组索引误传给了底层回调接口。正式版 Deno 2.8.1 已包含这项参数截断修正。

Promise API 为什么会提到 callback

Array.prototype.map 调用回调时会传入元素、索引和整个数组。unlink 本来只需要路径,受影响的 Deno Promise 包装器却把这三个实参全部保留,再把自己生成的 callback 追加到末尾。

底层收到的调用形状于是近似于 fs.unlink(path, 0, paths, callback)。回调式 unlink 在第二个位置寻找函数,看到索引 0 后就抛出类型错误。报错并不表示应用应该给 fs.promises.unlink 再补一个 callback。

这里与 map(parseInt) 把索引当进制 有相同的参数传递入口,但技术边界不同:parseInt 的进制参数本来就参与运算;本题是 Deno 的 Promise 兼容包装没有按底层接口需要的参数个数裁剪额外实参,Node 同一调用不会因此报 callback 类型错误。

显式只传入需要的路径

暂时无法更新运行时,可以在调用点使用包装函数。下面假设 paths 是应用已经核对、确实需要删除的文件清单:

import { unlink } from "node:fs/promises";

await Promise.all(paths.map((path) => unlink(path)));

这段会删除清单里的文件,不是只检查路径。示例没有替应用决定哪些文件可以删,也不负责递归清理目录。保留原来的路径边界、权限和业务完成记录,不要为避开类型错误扩大删除范围。

Promise.all 中一项拒绝,不会撤销其他已经完成的删除,也不会自动取消其他进行中的请求。原来失败的那次尝试同样应核对各文件状态,不能因为最后看见异常,就认定此前完全没有发生副作用。

还有其他需要额外参数的文件接口时,包装函数应明确传入其各自参数;不能把这个单参数例子机械套到 rename、writeFile 等接口上。

更新后仍要保留失败路径

Deno 2.8.1 的兼容实现为这些 Promise 包装器记录底层接口接受的最大参数数目,调用前丢弃超出的实参。它修正了 map(unlink) 的这个兼容差异,不会让其他签名天然适合作为 map 回调。

按实际服务、脚本或 CI 使用的运行时更新版本。在隔离目录里使用可丢弃的文件检查原来的调用入口:预期文件被删除,原始 callback 类型错误不再出现;文件本来不存在或没有权限时,仍应得到相应的文件系统错误。

若完整堆栈没有经过 Deno 的 node:fs Promise 包装,或更新后错误发生在另一层 callback 接口,应继续检查那个接口的调用签名。不要把所有出现 callback 字样的 TypeError 都归为这个版本问题。