Node.js v16 升级 v20 后 async 函数返回值变成 Pending 状态为什么?

文章导读
Node.js 从 v16 升级至 v20 后,若发现 async 函数返回值显示为 Promise { <pending> },这并非 Node.js 内核的行为变更,而是代码中遗漏 await 或依赖库升级导致接口异步化所致。
📋 目录
  1. 核心原因分析
  2. 排查与修复步骤
  3. 依赖库变更案例
  4. 验证方法
  5. 常见陷阱
  6. 参考来源
A A

Node.js 从 v16 升级至 v20 后,若发现 async 函数返回值显示为 Promise { <pending> },这并非 Node.js 内核的行为变更,而是代码中遗漏 await 或依赖库升级导致接口异步化所致。

核心结论:async 函数在所有现代 JavaScript 环境中均返回 Promise,显示 Pending 说明 Promise 尚未被解析或未使用 await 获取结果,需排查代码适配问题。

  • 确认调用处:检查调用 async 函数处是否遗漏了 await 关键字。
  • 更新依赖:将第三方依赖库更新至支持 Node.js v20 的版本。
  • 验证结果:通过 console.log 打印 await 后的结果确认值已解析。

核心原因分析

在 JavaScript 标准规范中,async 函数始终返回一个 Promise 对象,这一点在 Node.js v16 到 v20 之间没有改变。如果你在控制台看到 Promise { <pending> },通常是因为以下两个原因之一:

第一,代码直接打印了函数调用结果而没有等待。例如使用 console.log(asyncFn()) 而不是 console.log(await asyncFn())。在 Node.js 新版本中,控制台对 Promise 的 inspect 输出可能更加明确,导致之前被忽略的未 await 情况现在更明显。

第二,依赖库升级导致行为变化。某些库在适配新版本 Node.js 时,可能将原本同步的函数改为了异步函数,或者改变了内部 Promise 的处理逻辑,导致调用方原本不需要 await 的代码现在必须 await 才能拿到值。

查阅 Node.js 官方 CHANGELOG 可知,v20 并未修改 async 函数的基础返回机制,这更多是运行时期望值与实际返回类型不匹配的问题。

排查与修复步骤

按照以下步骤逐步排查,每一步完成后请重启服务验证。

1. 检查调用处是否缺失 await

在代码中搜索该函数名,确保调用处使用了 await。如果是顶层代码,确保文件后缀为.mjs 或 package.json 中设置了 "type": "module" 以支持顶层 await。

Node.js v16 升级 v20 后 async 函数返回值变成 Pending 状态为什么?
// 错误示例
const result = myAsyncFunc();
console.log(result); // 输出 Promise { <pending> }

// 正确示例
const result = await myAsyncFunc();
console.log(result); // 输出实际值

2. 检查依赖包兼容性

使用 npm ls 查看是否有依赖包报错或不兼容。升级主要依赖包到最新版本,查看其 changelog 是否有破坏性更新。

npm update
npm ls

3. 检查测试 runner 配置

如果你使用 Node.js 内置的 test runner,v18 之后行为更加严格。确保异步测试函数正确 await 或返回 Promise。

// 测试文件示例
import { test } from 'node:test';

test('异步测试', async () => {
  const data = await asyncFunc();
  // 确保这里 await 了
});

依赖库变更案例

在实际升级过程中,部分常用库的 major 版本更新可能伴随 API 异步化。例如某些数据库客户端或 HTTP 请求库,在旧版本中可能支持同步获取配置或连接状态,而在新版本中强制要求异步初始化。

若发现升级某依赖后出现 Pending 状态,请查阅该依赖的 Migration Guide,确认是否有函数签名从同步变为异步的情况,并相应调整调用代码。

验证方法

修改代码后,通过以下方式确认问题已解决:

  • 运行脚本,观察控制台输出是否不再是 Promise { <pending> },而是具体的数据值。
  • 检查应用日志,确认没有 UnhandledPromiseRejectionWarning 警告。
  • 如果是 API 服务,使用 curl 或 Postman 请求接口,确认响应体包含实际数据而非空对象或错误。

常见陷阱

  • 顶层 await 限制:在 CommonJS 模式(默认)下不支持顶层 await,强行使用会导致语法错误,需改为 ESM 或包裹在 async IIFE 中。
  • 回调函数混用:部分旧代码可能混合使用回调和 Promise,升级后可能导致执行顺序混乱,建议统一改为 async/await。
  • 测试环境差异:本地 REPL 环境与生产脚本环境对 Promise 的展示可能不同,不要仅依赖 REPL 的输出判断。

参考来源

  • Node.js 官方发布说明,CHANGELOG.md,https://github.com/nodejs/node/blob/main/CHANGELOG.md
  • MDN Web Docs,async function,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function