JavaScript Promise怎么用_如何避免then的链式嵌套?

Promise.then链式嵌套本质是显式返回Promise的同步写法错觉,未返回则链断裂;正确做法是每个then回调返回Promise或值,或用async/await语法糖扁平化。

Promise.then 链式嵌套的本质是同步写法错觉

很多人以为 then 嵌套是因为“必须等上一个异步完成”,其实根本原因是:没在每个 then 回调里返回新的 Promise。JavaScript 的 Promise 链不是自动扁平的,它靠你显式返回来决定下一级接收什么值。

常见错误写法:

fetch('/api/user')
  .then(res => res.json())
  .then(user => {
    fetch(`/api/posts?uid=${user.id}`)
      .then(res => res.json())
      .then(posts => {
        console.log({ user, posts });
      });
  });
这个写法里,最外层第二个 then 的回调函数没有 return,所以链就断了,后续逻辑变成“闭包内手动嵌套”,彻底失去 Promise 链的控制力和错误冒泡能力。

用 return Promise 实现扁平链式调用

只要每个 then 回调都返回一个 Promise(或可被 Promise.resolve() 包装的值),链就会自然延续。这是最直接、不依赖额外语法的解法。

  • 返回 fetch() 调用本身(它返回 Promise)
  • 返回 res.json()(它也返回 Promise)
  • 避免在 then 里写纯同步逻辑后不返回——除非你明确想把值透传下去

正确扁平写法:

fetch('/api/user')
  .then(res => res.json())
  .then(user =>
    fetch(`/api/posts?uid=${user.id}`)
      .then(res => res.json())
      .then(posts => ({ user, posts }))
  )
  .then(data => console.log(data))
  .catch(err => console.error(err));
注意第二层 then 里用括号包裹了整个内层链,并且末尾有 return(隐式,因为箭头函数单表达式体自动返回)。

async/await 是更自然的扁平化方案

它不是 Promise 的替代品,而是语法糖,底层仍基于 Promise。优势在于:代码看起来像同步,但执行仍是异步,且天然规避嵌套。

等价改写:

async function loadUserData() {
  try {
    const userRes = await fetch('/api/user');
    const user = await userRes.json();
    
    const postsRes = await fetch(`/api/posts?uid=${user.id}`);
    const posts = await postsRes.json();
    
    return { user, posts };
  } catch (err) {
    console.error(err);
  }
}
await 只能在 async 函数中使用;每个 await 后面必须是 Promise(否则会被 Promise.resolve() 包装);错误统一用 try/catch 捕获,不再依赖 .catch()

容易被忽略的陷阱:return undefined 和隐式拒绝

Promise 链里如果某个 then 回调既没 return,也没抛错,它会把 undefined 作为下一个 then 的输入值——这常导致后续 TypeError: Cannot read property 'xxx' of undefined,但错误堆栈指向下游,排查困难。

另一个坑是:在 async 函数中忘记 await 某个 Promise,比如写成 const posts = fetch(...)(没加 await),那 posts 就是 Promise 对象本身,不是解析后的数据,后续操作大概率出错。

建议始终开启 ESLint 规则:no-return-await(防止冗余 await)、require-await(强制 async 函数里有 await)、promise/no-nesting(检测深层嵌套)。