JavaScript / TypeScript 易踩坑知识点 —— 异步编程类

JavaScript / TypeScript 易踩坑知识点 ------ 异步编程类

一、回调地狱(Callback Hell)

问题说明

多层异步回调嵌套会导致代码难以阅读和维护,形成"箭头型"代码。

踩坑示例

javascript 复制代码
// ❌ 回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getFinalData(c, function(d) {
        console.log(d)
      })
    })
  })
})

修复方案

javascript 复制代码
// ✅ 方案1:Promise 链式调用
getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => getFinalData(c))
  .then(d => console.log(d))
  .catch(err => console.error(err))

// ✅ 方案2:async/await(推荐)
async function fetchData() {
  try {
    const a = await getData()
    const b = await getMoreData(a)
    const c = await getEvenMoreData(b)
    const d = await getFinalData(c)
    console.log(d)
  } catch (err) {
    console.error(err)
  }
}

二、Promise 错误处理缺失

问题说明

Promise 链中忘记添加 .catch() 会导致错误被静默吞掉,难以排查。

踩坑示例

javascript 复制代码
// ❌ 错误被静默吞掉
fetchData()
  .then(data => process(data))
  .then(result => save(result))
  // 没有 .catch(),任何错误都不会被捕获

// ✅ 正确:链式末尾加 .catch()
fetchData()
  .then(data => process(data))
  .then(result => save(result))
  .catch(err => console.error('处理失败:', err))

// ✅ 更推荐:async/await + try/catch
async function main() {
  try {
    const data = await fetchData()
    const result = await process(data)
    await save(result)
  } catch (err) {
    console.error('处理失败:', err)
  }
}

中间步骤遗漏 catch

javascript 复制代码
// ❌ 第一步的 catch 遗漏了
fetchData()
  .catch(err => console.error('获取数据失败'))  // 只 catch 了第一步
  .then(data => process(data))  // process 报错不会被捕获
  .catch(err => console.error('处理失败'))

// ✅ 统一在末尾 catch
fetchData()
  .then(data => process(data))
  .then(result => save(result))
  .catch(err => {
    console.error('出错了:', err)
  })

三、async/await 中忘记 try/catch

问题说明

async 函数返回的是 Promise,内部的错误如果没有被 try/catch 捕获,会变为未处理的 Promise rejection。

踩坑示例

javascript 复制代码
// ❌ 忘记 try/catch
async function getUser() {
  const res = await fetch('/api/user')  // 网络错误会未捕获
  const data = await res.json()
  return data
}

// 调用方如果没有 catch,错误会静默失败
getUser()  // 错误未被处理

// ✅ 正确:内部或外部捕获
// 方案1:内部捕获
async function getUser() {
  try {
    const res = await fetch('/api/user')
    const data = await res.json()
    return data
  } catch (err) {
    console.error('获取用户失败:', err)
    throw err  // 重新抛出,让调用方处理
  }
}

// 方案2:外部捕获(推荐)
async function main() {
  try {
    const user = await getUser()
    console.log(user)
  } catch (err) {
    console.error('请求失败:', err)
  }
}

四、Promise.all 的错误处理

问题说明

Promise.all 中任何一个 Promise 失败,整个 all 会立即 reject,且其他请求会被中断。

踩坑示例

javascript 复制代码
// ❌ 任何一个失败,其他请求被中断
const [users, posts, comments] = await Promise.all([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
])

// ✅ 方案1:用 Promise.allSettled 等待所有完成
const results = await Promise.allSettled([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
])

results.forEach((result, i) => {
  if (result.status === 'fulfilled') {
    console.log(`请求 ${i} 成功:`, result.value)
  } else {
    console.log(`请求 ${i} 失败:`, result.reason)
  }
})

// ✅ 方案2:给每个请求单独加 catch
async function safeFetch(url) {
  try {
    const res = await fetch(url)
    return await res.json()
  } catch (err) {
    return { error: err.message }
  }
}

const [users, posts, comments] = await Promise.all([
  safeFetch('/api/users'),
  safeFetch('/api/posts'),
  safeFetch('/api/comments')
])

五、async 函数中的 for 循环

问题说明

在循环中使用 await 时,for...offorEach 的行为完全不同。

踩坑示例

javascript 复制代码
const urls = ['/api/a', '/api/b', '/api/c']

// ❌ forEach 中的 await 不会等待
urls.forEach(async (url) => {
  const data = await fetch(url)
  console.log(data)
})
console.log('所有请求完成')  // 这行会先执行!

// ✅ 用 for...of 按顺序执行
for (const url of urls) {
  const data = await fetch(url)
  console.log(data)
}

// ✅ 用 Promise.all 并行执行(不关心顺序时)
const results = await Promise.all(
  urls.map(url => fetch(url))
)

六、setTimeout 与 async/await 的混淆

问题说明

setTimeout 返回 undefined,不是 Promise,不能直接 await

踩坑示例

javascript 复制代码
// ❌ setTimeout 返回 undefined,await 没有意义
async function wait() {
  await setTimeout(() => console.log('2秒后'), 2000)
  console.log('这行会立即执行!')  // 不会等 2 秒
}

// ✅ 正确:将 setTimeout 包装为 Promise
function sleep(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

async function wait() {
  await sleep(2000)
  console.log('2秒后执行')  // ✅ 正确等待
}

七、竞态条件(Race Condition)

问题说明

在快速输入(如搜索框)时,多个异步请求可能以不确定顺序返回,导致旧请求的结果覆盖新请求的结果。

踩坑示例

javascript 复制代码
// ❌ 竞态条件:用户快速输入 "abc",请求顺序 a->ab->abc,
// 但返回顺序可能是 abc->a->ab,导致显示错误的结果

const search = async (keyword: string) => {
  const res = await fetch(`/api/search?q=${keyword}`)
  const data = await res.json()
  renderResults(data)  // ⚠️ 可能是旧请求的结果
}

// ✅ 方案1:用 AbortController 取消旧请求
let controller: AbortController | null = null

const search = async (keyword: string) => {
  controller?.abort()  // 取消上一次的请求
  controller = new AbortController()

  try {
    const res = await fetch(`/api/search?q=${keyword}`, {
      signal: controller.signal
    })
    const data = await res.json()
    renderResults(data)
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('请求已被取消')
    }
  }
}

// ✅ 方案2:用序列号防覆盖
let seq = 0

const search = async (keyword: string) => {
  const currentSeq = ++seq
  const res = await fetch(`/api/search?q=${keyword}`)
  const data = await res.json()

  if (currentSeq !== seq) return  // 有更新的请求,丢弃旧结果
  renderResults(data)
}

八、Promise 构造函数反模式

问题说明

new Promise 包装已有的 Promise API 是反模式,会破坏错误传播。

踩坑示例

javascript 复制代码
// ❌ 反模式:不必要的 Promise 包装
function getUsers() {
  return new Promise((resolve, reject) => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => resolve(data))
      .catch(err => reject(err))
  })
}

// ✅ 直接返回 Promise
function getUsers() {
  return fetch('/api/users').then(res => res.json())
}

// ✅ async/await 写法
async function getUsers() {
  const res = await fetch('/api/users')
  return await res.json()
}

总结对照表

坑点 错误写法 正确写法
回调地狱 多层嵌套回调 async/await
Promise 错误吞掉 不加 .catch() 链式末尾加 .catch()
async 未捕获 不在 try/catch 中调用 外部 try/catch 或内部捕获
Promise.all 中断 一个失败全部中断 Promise.allSettled
forEach + await forEach 中用 await for...of
setTimeout + await await setTimeout() 包装为 Promise
竞态条件 不控制请求顺序 AbortController 或序列号
Promise 反模式 new Promise 包装已有 Promise 直接返回 Promise
相关推荐
tsumikistep1 小时前
【前端】Vue + Axios 跨域问题实战:7070 代理转发 8080 + 401 报错分析(黑马外卖)
前端·javascript·vue.js
七夜zippoe1 小时前
为什么 2026 年每个 Java 团队都该懂 AI Agent
java·开发语言·人工智能
znnnk1 小时前
【Python】GUI 开发从入门到实战(三):PyQt/PySide 进阶之路
开发语言·python·pyqt
Despacito10062 小时前
Java后端性能探查工具速查表
java·开发语言
晊晌_h2 小时前
嵌入式从0到精通——Linux C|TCP 并发服务器实现方案详解
服务器·开发语言·tcp/ip
传奇开心果编程2 小时前
【Rust入门知识点学与练】第17课:闭包 Closures
开发语言·学习·rust
广州灵眸科技有限公司3 小时前
从手动三步到上电即连:4G模组systemd开机自连方案
linux·运维·服务器·开发语言·网络·人工智能·php
今夜有雨.3 小时前
C# 学习文档(零基础入门)
开发语言·学习·c#
默_笙3 小时前
🧀 用户访问一个网站到底经历了什么?全栈部署的"城市观光"指南
前端·javascript