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...of 和 forEach 的行为完全不同。

踩坑示例

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
相关推荐
神仙别闹6 分钟前
基于C语言实现 TINY+ 编译器
c语言·开发语言·数据库
Wang's Blog10 分钟前
Java 项目实战: 外卖平台优化-从库Slave配置与主从复制验证
java·开发语言
0nirvana023 分钟前
从普通 script 到 ESM:一文搞懂 JavaScript 模块、IIFE 和 window
javascript
知识分享小能手27 分钟前
C++ 学习教程,从入门到精通,C++内存模型和名称空间 — 详细知识点总结(9)
开发语言·c++·学习
2601_9622186135 分钟前
c++中多重继承与虚继承的实现
开发语言·c++
我是小白呀1 小时前
17-企业Workflow引擎怎么选:定时、队列、n8n、Temporal与Camunda
java·开发语言·人工智能·dubbo·workflow
维克兜率天1 小时前
【维克】均值回归:跌多了会涨,涨多了会跌
开发语言·笔记·python·算法·均值算法·回归·量化
实心儿儿1 小时前
Qt — Qt 窗口
开发语言·qt
2601_962218611 小时前
C++中分配器allocator的实现
开发语言·c++
wshzd1 小时前
LLM之Agent(104)|DeepSeek-Harness(十三)ReactLoopAgent 总览:kick → turn → step
开发语言·前端·javascript