JavaScript 异步管理的一种实践

在实际项目中,JavaScript 异步编程往往缺乏统一的管理规范,导致代码难以维护。这篇文章分享一套实践方案:以 Promise/async-await 作为统一的异步编程基础,通过禁止 Floating Promises 来确保所有 Promise 都得到妥善处理。在这个基础上,我们需要区分两种不同的异步执行方式------Fire-and-Forget 和 Await,并分别管理它们。对于前者,用 fireAndForget 工具函数明确标记意图;对于后者,通过 AbortSignal 实现取消控制,同时正确处理 AbortError 以免将其误当作业务异常。最后,借助 Lint 规则来约束团队的编码习惯,避免写出有问题的代码。

统一异步编程方案

要谈异步管理,得先从统一方案说起。如果团队里有人用 Callback,有人用 Promise,还有人在用 Generator,代码就会变得很难维护。这一节我们先回顾一下 JavaScript 异步方案的演进历程,看看为什么要避免 Callback、为什么不推荐用 Generator 处理异步,以及为什么 Promise/async-await 是更好的选择。

JavaScript 异步方案演进

JavaScript 异步编程的发展经历了几个重要阶段,每一代方案都在解决上一代的问题:

1. Callback

最早的异步解决方案,通过函数参数传递回调:

javascript 复制代码
// Callback 方式
fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

2. Promise

ES6 引入,解决回调地狱问题,提供链式调用:

javascript 复制代码
// Promise 方式
fetch('/api/data')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error))

3. Generator

ES6 同期引入的特性,配合 Promise 可以实现类似同步的异步代码:

javascript 复制代码
// Generator 方式(需要执行器运行)
function* fetchData() {
  const response = yield fetch('/api/data')
  const data = yield response.json()
  return data
}

但 Generator 的本意是生成值序列(Iterator),不是为异步设计的。

4. async/await

ES2017 引入,专为异步设计的语法糖:

javascript 复制代码
// async/await 方式
async function fetchData() {
  const response = await fetch('/api/data')
  const data = await response.json()
  return data
}

为什么提出 async/await?

你可能会问,既然 Generator 能做异步,为什么还要 async/await?其实 Generator 本质上是为迭代器设计的,yield 的语义是"生成下一个值",用来处理异步只是一种巧妙的借用。而 async/await 是专门为异步设计的:

  • 语义更明确:await 就是"等待异步操作",一看就懂
  • 原生支持,不需要额外的执行器库
  • 错误处理更简单:直接用 try/catch 就行
  • 和现代异步控制(如 AbortSignal)配合得更好

避免使用 Callback 处理新的异步操作

为什么要避免 Callback?

Callback 是最早的异步方案,但问题也很明显:

  • 回调地狱(Callback Hell):多层嵌套让代码变成"圣诞树",难读又难改
  • 错误处理繁琐:每个回调都要单独处理错误,稍不留神就漏掉了
  • 无法取消:操作一旦启动就停不下来

示例对比:

javascript 复制代码
// ❌ Callback 嵌套
getData(id, (err, data) => {
  if (err) return handleError(err)
  processData(data, (err, result) => {
    if (err) return handleError(err)
    saveResult(result, (err, saved) => {
      if (err) return handleError(err)
      console.log('Success')
    })
  })
})

// ✅ async/await
try {
  const data = await getData(id)
  const result = await processData(data)
  await saveResult(result)
  console.log('Success')
} catch (err) {
  handleError(err)
}

为什么不能完全禁止 Callback?

不过也不是说要把 Callback 赶尽杀绝,有些场景确实离不开它:

  • DOM 事件监听:addEventListener 必须传回调函数
  • 数组方法:mapfilterforEach 也需要回调

如何避免使用 Callback?

方案 1:Promise 包装遗留 API

javascript 复制代码
// 遗留的 Callback API
function legacyApi(param, callback) {
  /* ... */
}

// ✅ Promise 包装
function modernApi(param) {
  return new Promise((resolve, reject) => {
    legacyApi(param, (err, result) => {
      if (err) reject(err)
      else resolve(result)
    })
  })
}

// 使用
const result = await modernApi('value')

方案 2:使用 signal-timers 替代原生定时器

原生的 setTimeoutsetInterval 也是基于 Callback 的,而且取消起来很麻烦。signal-timers 这个库可以让定时器返回 Promise,还支持 AbortSignal:

javascript 复制代码
import { setTimeout } from 'signal-timers'

// ❌ 原生 setTimeout(Callback + 无法取消)
setTimeout(() => {
  console.log('Timeout')
}, 1000)

// ✅ signal-timers(Promise + 可取消)
await setTimeout(1000, { signal })
console.log('Timeout')

禁止使用 Generator 处理异步

为什么要禁止 Generator 处理异步?

虽然 Generator 理论上能处理异步,但并不推荐:

  • 语义不清晰 :看到 yield 会以为在生成值,其实是在等异步,容易混淆
  • 不是为异步设计:Generator 本来是用来做迭代器的,处理异步只是"借用"
  • 已有更好方案:async/await 是专门为异步设计的,何必舍近求远?

反例和正例对比

javascript 复制代码
// ❌ 禁止:用 Generator 处理异步
function* fetchData() {
  const response = yield fetch('/api/data')
  const data = yield response.json()
  return data
}

// ✅ 推荐:用 async/await 处理异步
async function fetchData() {
  const response = await fetch('/api/data')
  const data = await response.json()
  return data
}

通过 ESLint 禁止 Generator 处理异步

可以使用 no-restricted-syntax 规则禁止异步 Generator:

javascript 复制代码
// eslint.config.js
export default [
  {
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector: 'FunctionDeclaration[async=true][generator=true]',
          message: '禁止使用异步 Generator 函数',
        },
        {
          selector: 'FunctionExpression[async=true][generator=true]',
          message: '禁止使用异步 Generator 表达式',
        },
      ],
    },
  },
]

例外:Generator 用于迭代器是正确的

Generator 回归本职工作------生成值序列,这才是它的正确打开方式:

javascript 复制代码
// ✅ 正确的 Generator 用法:迭代器
function* range(start, end) {
  for (let i = start; i < end; i++) {
    yield i
  }
}

// 使用
for (const num of range(1, 5)) {
  console.log(num) // 1, 2, 3, 4
}

// ✅ 正确的 Generator 用法:无限序列
function* fibonacci() {
  let [a, b] = [0, 1]
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

推荐使用 Promise/async-await

为什么推荐 Promise/async-await?

说了这么多不推荐的,那推荐什么?答案是 Promise/async-await:

  • 代码清晰:写起来就像同步代码一样,一眼能看懂
  • 错误处理简单:统一用 try/catch,不用到处写错误处理
  • 可组合Promise.allPromise.race 这些组合器很好用
  • 可取消:配合 AbortSignal 可以随时取消操作

标准的异步函数模式

javascript 复制代码
// ✅ 标准异步函数模式
async function fetchData(id, signal) {
  try {
    const response = await fetch(`/api/data/${id}`, { signal })
    const data = await response.json()
    return data
  } catch (error) {
    // 记录错误
    console.error('Failed to fetch data:', error)
    // 重新抛出,让调用方处理
    throw error
  }
}

统一的错误处理

javascript 复制代码
// ✅ 统一错误处理
async function processUserData(userId) {
  try {
    const user = await fetchUser(userId)
    const orders = await fetchOrders(userId)
    const result = await processData(user, orders)
    return result
  } catch (error) {
    // 区分取消错误和业务错误
    if (error.name === 'AbortError') {
      console.log('操作已取消')
      return null
    }
    // 记录错误并重新抛出
    logger.error('处理用户数据失败', { userId, error })
    throw error
  }
}

并行操作

javascript 复制代码
// ✅ 使用 Promise.all 并行执行
async function loadDashboard(signal) {
  const [user, orders, stats] = await Promise.all([
    fetchUser(signal),
    fetchOrders(signal),
    fetchStats(signal),
  ])

  return { user, orders, stats }
}

// ✅ 使用 Promise.race 竞速
async function fetchWithTimeout(url, timeout) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), timeout),
    ),
  ])
}

禁止 Floating Promises

统一用 Promise/async-await 只是第一步,还得确保每个 Promise 都被妥善处理。如果一个 Promise 创建后不做任何事情,就会"漂浮"在代码里,导致各种奇怪的问题。这一节我们来聊聊 Floating Promise 是什么、有什么危害,以及如何用 @typescript-eslint/no-floating-promises 规则来杜绝它。

什么是 Floating Promise

简单说,Floating Promise 就是那些创建后没人管的 Promise------既不 await,也不用 .then/.catch 处理。它们就这么"漂"在代码里,出了问题也没人知道。

javascript 复制代码
// ❌ Floating Promise 示例
async function saveData(data) {
  fetchUser() // 返回 Promise,但没有被处理
  processData(data) // 返回 Promise,但没有被处理
  console.log('Done')
}

// 执行流程:
// 1. fetchUser() 启动(返回 Promise)
// 2. processData() 启动(返回 Promise)
// 3. 立即输出 'Done'
// 4. fetchUser() 和 processData() 可能还在执行
// 5. 如果它们失败,错误不会被捕获

在这个例子中,fetchUser()processData() 返回的 Promise 没有被 awaitreturn 或使用 .then()/.catch() 处理,就是 Floating Promise。

Floating Promise 的危害

Floating Promise 看起来无害,实际上会带来不少麻烦。

1. 错误被忽略,导致静默失败

最常见的问题就是错误被"吞"了,用户以为操作成功了,其实早就失败了。

javascript 复制代码
// ❌ 问题:保存失败但没有任何提示
async function saveUser(user) {
  database.save(user) // 如果保存失败,错误被完全忽略
  showSuccessMessage('保存成功')
}

// 实际运行:
// - database.save() 返回 Promise 但没有等待
// - 如果 Promise reject,错误不会被捕获
// - 用户看到"保存成功",但数据可能保存失败

2. 时序不确定,导致竞态条件

javascript 复制代码
// ❌ 问题:通知可能在更新完成前发送
async function updateAndNotify(userId, data) {
  updateUser(userId, data) // 没有 await
  sendNotification(userId, '更新成功') // 立即执行
}

// 执行顺序:
// 1. updateUser() 开始执行(返回 Promise)
// 2. sendNotification() 立即执行
// 3. updateUser() 可能还在进行中
// 结果:用户收到"更新成功",但数据还未更新

3. 难以调试,调用栈丢失

javascript 复制代码
// ❌ 问题:错误发生时难以追溯来源
function processData() {
  fetchData().then((data) => {
    throw new Error('处理失败')
  })
  // 没有 .catch(),错误不会被捕获
}

// 控制台输出:
// Uncaught (in promise) Error: 处理失败
//     at <anonymous>
//
// 问题:调用栈没有显示是在 processData() 中出错

4. 资源泄漏

javascript 复制代码
// ❌ 问题:未处理的 Promise 可能持有资源引用
function startPolling() {
  setInterval(() => {
    fetchData() // 每次都创建新的 Floating Promise
      .then((data) => updateUI(data))
    // 如果没有 .catch(),错误会累积
  }, 1000)
}

// 问题:
// - 每秒创建新的 Promise,但错误没有被处理
// - 可能导致内存泄漏
// - 错误累积可能影响性能

如何禁止 Floating Promise

@typescript-eslint/no-floating-promises 这个规则专门用来检测 Floating Promise。它要求所有 Promise 必须用以下三种方式之一处理:

  1. await 等待 - 等它完成再继续
  2. return 返回 - 让调用方去处理
  3. .then()/.catch().finally() 处理 - 链式处理结果和错误

配置规则

javascript 复制代码
// eslint.config.js
export default [
  {
    rules: {
      '@typescript-eslint/no-floating-promises': [
        'error',
        {
          ignoreVoid: false, // 禁止使用 void 标记 Promise
        },
      ],
    },
  },
]

代码示例

typescript 复制代码
// ❌ 错误:直接调用异步函数,没有处理返回的 Promise
async function bad1() {
  fetchData() // Error: Promises must be awaited, end with a call to .catch, or end with a call to .then with a rejection handler
}

// ❌ 错误:只有 .then() 没有 .catch(),rejection 没有被处理
async function bad2() {
  fetch('/api/data')
    .then((res) => res.json())
    .then((data) => console.log(data)) // Error
}

// ✅ 正确:方式 1 - 使用 await
async function good1() {
  await fetchData()
}

async function good2() {
  try {
    await fetchData()
  } catch (error) {
    console.error('Failed:', error)
  }
}

// ✅ 正确:方式 2 - 使用 return
async function good3() {
  return fetchData()
}

async function good4(shouldFetch: boolean) {
  if (shouldFetch) {
    return fetchData()
  }
  return null
}

// ✅ 正确:方式 3 - 使用 .then()/.catch()
async function good5() {
  fetchData()
    .then((data) => console.log(data))
    .catch((err) => console.error(err))
}

async function good6() {
  fetchData()
    .then((data) => processData(data))
    .catch((err) => {
      console.error('Error:', err)
      return null
    })
    .then((result) => console.log('Result:', result))
}

可控的异步操作

解决了 Floating Promise 的问题,我们还需要关注异步操作的执行方式。JavaScript 里的异步操作大致分两类:Fire-and-Forget(发出去就不管了)和 Await(等结果)。前者包括了 callback、promise.then.catch 和 floating promise,这些建议明确标记一下,省得别人以为你忘了处理。后者会阻塞代码执行,如果不及时取消容易导致内存泄漏或状态混乱,建议用 AbortSignal 来控制。另外,取消操作会抛出 AbortError,但这不是业务错误,只是个取消标记,别当异常处理。

Fire-and-Forget vs Await

异步操作的执行方式分两种,理解它们的区别很重要。

Fire-and-Forget:发出去就不管了

Fire-and-Forget 就是发起一个异步操作,然后立刻继续往下走,不等结果。callback、promise.then.catch、还有前面提到的 floating promise 都属于这一类。

javascript 复制代码
// callback 的 Fire-and-Forget
function logToServer(message) {
  sendLog(message, (error) => {
    if (error) {
      console.error('日志发送失败:', error)
    }
  })
  // 立即返回,不等待 sendLog 完成
}

logToServer('用户登录')
console.log('继续执行') // 立即输出

// promise.then.catch 的 Fire-and-Forget
function trackEvent(event) {
  fetch('/api/track', {
    method: 'POST',
    body: JSON.stringify(event),
  })
    .then(() => console.log('埋点发送成功'))
    .catch((error) => console.error('埋点发送失败:', error))
  // 立即返回,不等待 fetch 完成
}

trackEvent({ action: 'click', target: 'button' })
console.log('继续执行') // 立即输出

// floating promise 的 Fire-and-Forget
function sendNotification(message) {
  // 直接调用返回 Promise 的函数,什么都不做
  fetch('/api/notify', {
    method: 'POST',
    body: JSON.stringify({ message }),
  })
  // 立即返回,不等待 fetch 完成
  // 也不处理结果或错误
}

sendNotification('新消息')
console.log('继续执行') // 立即输出

特点:

  • 注册回调后立即返回,不等结果
  • 不会阻塞后面的代码
  • 一旦发出去就收不回来了(回调必然执行)
  • 适合不关心结果的场景,比如日志上报、埋点之类的

Await:等结果

Await 就不一样了,它会等异步操作完成才继续。用 await 的时候,代码会暂停在那里,等操作完成才往下走。

javascript 复制代码
// await 等待异步操作
async function loadUserData(userId, shouldCancel) {
  // await 暂停在这里,等待 fetch 完成
  const response = await fetch(`/api/users/${userId}`)

  // await 完成后,检查是否需要取消
  if (shouldCancel()) {
    return null // 直接返回,后续代码不执行
  }

  // 继续处理数据
  const user = await response.json()

  // 再次检查
  if (shouldCancel()) {
    return null
  }

  return user
}

// 使用
let cancelled = false
const controller = {
  cancel: () => {
    cancelled = true
  },
}

const userData = loadUserData(123, () => cancelled)

// 用户切换页面时取消
controller.cancel()

const user = await userData
if (user) {
  console.log('用户数据:', user)
} else {
  console.log('操作已取消')
}

特点:

  • 会暂停执行,等结果
  • 阻塞后面的代码(直到拿到结果)
  • await 完成后可以检查条件,决定要不要继续
  • 适合需要结果的场景,比如加载数据、处理业务逻辑这些

对比:Fire-and-Forget vs Await

特性 Fire-and-Forget Await
执行方式 同步流程中注册回调,立即返回 异步流程中等待,暂停执行
是否阻塞 不阻塞后续代码 阻塞后续代码,等待结果
是否可取消 不可取消,回调必然执行 可取消,await 完成后可中断
错误处理 在回调中单独处理 统一使用 try-catch 处理
典型实现 callback、promise.then.catch、floating promise async/await
典型使用场景 日志上报、埋点、通知、预加载 数据加载、业务逻辑、API 调用

明确标记 Fire-and-Forget

Fire-and-Forget 虽然合理,但最好明确标记一下。

为什么需要明确标记?

如果直接调用一个返回 Promise 的函数然后就不管了,代码审查的时候别人会懵:这是有意不等待,还是忘了写 await?

javascript 复制代码
// ❌ 代码意图不清晰:是忘记 await 了,还是有意不等待?
function handleClick() {
  fetch('/api/track', {
    method: 'POST',
    body: JSON.stringify({ action: 'click' }),
  })
  // 继续执行其他逻辑
  navigateToNextPage()
}

明确标记可以解决这个问题:

  • 区分有意和疏忽:告诉审查者,我是故意不等的
  • 统一错误管理:集中处理 Fire-and-Forget 的错误
  • 提高可读性:一眼就知道是咋回事

实现方式:fireAndForget 工具函数

typescript 复制代码
/**
 * 明确标记 Fire-and-Forget 操作
 * 用于不需要等待结果的异步操作(日志、埋点等)
 * Fire-and-Forget 不会阻塞后续代码执行,因此不需要抛出 AbortError
 */
function fireAndForget(promise: Promise<unknown>): void {
  promise.catch((error) => {
    // 忽略 AbortError,因为 Fire-and-Forget 不需要取消
    if (error.name === 'AbortError') {
      return
    }
    // 记录其他错误,但不影响主流程
    console.error('Fire-and-Forget 操作失败:', error)
  })
}

// ✅ 明确标记 Fire-and-Forget
function trackEvent(event) {
  fireAndForget(
    fetch('/api/track', {
      method: 'POST',
      body: JSON.stringify(event),
    }),
  )
}

为什么 Await 需要及时取消

Fire-and-Forget 发出去就不管了,Await 可不一样,它会等结果还要执行后续逻辑。如果不及时取消,问题可就大了。

1. 内存泄漏和资源浪费

javascript 复制代码
// ❌ 问题:轮询没有被取消
async function startPolling() {
  while (true) {
    const data = await fetchData()
    updateUI(data)
    await sleep(5000)
  }
}

// 用户离开页面后,startPolling 仍在运行
// 持续消耗网络和内存资源

2. 状态不一致和竞态条件

javascript 复制代码
// ❌ 问题:快速切换时,旧请求可能后完成
async function loadUser(userId) {
  const user = await fetchUser(userId)
  displayUser(user) // 可能显示错误的用户
}

// 执行顺序:
// 1. loadUser(1) 开始
// 2. loadUser(2) 开始
// 3. loadUser(2) 完成,显示用户 2
// 4. loadUser(1) 完成,显示用户 1 ❌ 错误

3. 执行无效操作

javascript 复制代码
// ❌ 问题:组件卸载后仍在执行
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    ;(async () => {
      const data = await fetchUser(userId)
      // 组件可能已卸载,但仍在 setState
      setUser(data) // 警告:Can't perform a React state update on an unmounted component
    })()
  }, [userId])

  return <div>{user?.name}</div>
}

使用 AbortSignal 取消 Await

AbortController 是 Web 标准的取消机制,不少 API 都支持:

  • fetch API:取消网络请求
  • addEventListener:取消事件监听
  • signal-timers :取消定时器(原生 setTimeout 不支持,得用 signal-timers 库)

推荐的做法是:异步函数接受一个 AbortSignal 参数,在每次 await 之后检查一下要不要取消

javascript 复制代码
async function fetchData(url, signal) {
  // 1. 函数开始时检查
  signal?.throwIfAborted()

  // 2. 传递 signal 给支持的 API
  const response = await fetch(url, { signal })

  // 3. 长时间操作中多次检查
  const data = await response.json()
  signal?.throwIfAborted()

  return data
}

// 使用
const controller = new AbortController()

try {
  const data = await fetchData('/api/data', controller.signal)
  console.log(data)
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('操作已取消')
  }
}

// 取消操作
controller.abort()

正确处理 AbortError

AbortError 不是真正的错误,只是个取消信号。千万别把它当业务错误处理,下面几种处理方式可以参考:

不上报 AbortError:取消操作是正常行为,没必要上报到监控系统:

javascript 复制代码
function reportError(error) {
  // 忽略 AbortError
  if (error.name === 'AbortError') {
    return
  }
  // 上报业务错误
  errorMonitoring.report(error)
}

try-catch 中直接返回:碰到 AbortError 就直接返回,不走错误处理流程:

javascript 复制代码
async function loadData(signal) {
  try {
    const data = await fetchData(signal)
    return data
  } catch (error) {
    // AbortError 直接返回,不处理
    if (error.name === 'AbortError') {
      return null
    }
    // 业务错误才需要处理
    console.error('加载数据失败:', error)
    showErrorToast('加载失败')
    throw error
  }
}

全局错误处理中忽略:全局错误处理器应该忽略 AbortError:

javascript 复制代码
// 全局 Promise 错误处理
window.addEventListener('unhandledrejection', (event) => {
  if (event.reason?.name === 'AbortError') {
    // 忽略 AbortError
    event.preventDefault()
    return
  }
  // 处理其他错误
  reportError(event.reason)
})

fireAndForget 中忽略 AbortError:Fire-and-Forget 不会阻塞后续代码执行,因此不需要抛出 AbortError:

typescript 复制代码
function fireAndForget(promise: Promise<unknown>): void {
  promise.catch((error) => {
    // 忽略 AbortError,因为 Fire-and-Forget 不需要取消
    if (error.name === 'AbortError') {
      return
    }
    // 记录其他错误
    console.error('Fire-and-Forget 操作失败:', error)
  })
}

异步编程的规范

除了上面提到的实践,异步编程里还有一些常见的坑需要注意。这一节介绍几个实用的 Lint 规则,它们能帮你避开 Promise 误用、不必要的包装、callback 和 Promise 混用等问题,让异步代码写得更规范。

no-misused-promises (eslint)

规则说明: 禁止在不该用 Promise 的地方用 Promise。

为什么需要这个规则?

Promise 是异步的,在需要同步值的地方用它会出问题:

typescript 复制代码
// ❌ 错误:条件表达式中使用 Promise
if (await fetchData()) {
  // fetchData 返回 Promise,总是 truthy
}

// ❌ 错误:逻辑运算中使用 Promise
const result = fetchData() || defaultValue
// fetchData 返回 Promise 对象,总是 truthy,永远不会使用 defaultValue

// ❌ 错误:在事件处理器中返回 Promise
button.addEventListener('click', async () => {
  await handleClick()
  return true // 返回 Promise<boolean>,而不是 boolean
})

正确做法:

typescript 复制代码
// ✅ 正确:await 后再使用
const data = await fetchData()
if (data) {
  // 使用数据
}

// ✅ 正确:await 后再进行逻辑运算
const data = (await fetchData()) || defaultValue

// ✅ 正确:不返回值,或返回 void
button.addEventListener('click', async () => {
  await handleClick()
  // 不返回值
})

配置:

javascript 复制代码
// eslint.config.js
export default [
  {
    rules: {
      '@typescript-eslint/no-misused-promises': 'error',
    },
  },
]

no-return-wrap (oxlint)

规则说明: 禁止多余的 Promise 包装。

为什么需要这个规则?

async 函数会自动把返回值包装成 Promise,你再手动包一层就多余了:

javascript 复制代码
// ❌ 错误:多余的 Promise 包装
async function fetchData() {
  return Promise.resolve(data) // 会变成 Promise<Promise<T>>
}

async function getData() {
  return new Promise((resolve) => {
    resolve(data) // 多余的包装
  })
}

// ✅ 正确:直接返回值
async function fetchData() {
  return data // 自动包装成 Promise<T>
}

async function getData() {
  return data
}

no-promise-in-callback (oxlint)

规则说明: 别在 callback 里用 Promise。

为什么需要这个规则?

callback 和 Promise 混在一起,错误追踪起来很头疼。统一用 async/await 就好了:

javascript 复制代码
// ❌ 错误:在 callback 中使用 Promise
function processData(data, callback) {
  fetchData()
    .then((result) => {
      callback(null, result)
    })
    .catch((error) => {
      callback(error)
    })
}

// ✅ 正确:使用 async/await
async function processData(data) {
  try {
    const result = await fetchData()
    return result
  } catch (error) {
    throw error
  }
}

prefer-await-to-then (oxlint)

规则说明: 优先用 await,少用 .then()。

为什么需要这个规则?

await 写起来更简洁,错误处理也更统一:

javascript 复制代码
// ❌ 不推荐:使用 .then()
function loadData() {
  return fetchData()
    .then((data) => processData(data))
    .then((result) => saveData(result))
    .catch((error) => handleError(error))
}

// ✅ 推荐:使用 await
async function loadData() {
  try {
    const data = await fetchData()
    const result = await processData(data)
    await saveData(result)
  } catch (error) {
    handleError(error)
  }
}

prefer-promise-reject-errors (oxlint)

规则说明: reject 的时候必须传 Error 对象。

为什么需要这个规则?

Error 对象会保留堆栈信息,调试的时候能快速定位问题:

javascript 复制代码
// ❌ 错误:reject 字符串
Promise.reject('Error occurred')

async function fetchData() {
  if (!valid) {
    throw 'Invalid data' // 也是错误
  }
}

// ✅ 正确:reject Error 对象
Promise.reject(new Error('Error occurred'))

async function fetchData() {
  if (!valid) {
    throw new Error('Invalid data')
  }
}

require-await (oxlint)

规则说明: async 函数里必须有 await 或者返回 Promise。

为什么需要这个规则?

如果一个 async 函数里连 await 都没有,那 async 关键字就白加了,还不如删掉:

javascript 复制代码
// ❌ 错误:async 函数中没有 await
async function getData() {
  return data // 不需要 async
}

// ✅ 正确:移除 async
function getData() {
  return data
}

// ✅ 正确:包含 await
async function fetchData() {
  const result = await fetch('/api/data')
  return result
}
相关推荐
转转技术团队2 小时前
转转UI自动化走查方案探索
前端
yzx9910132 小时前
基于Flask的智能语音增强系统模拟
前端·javascript·html
青衫码上行2 小时前
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式
java·前端·javascript·学习·正则表达式
草帽lufei2 小时前
解锁AI新维度:深入体验Google Antigravity的Gemini3模型
前端·ai编程·gemini
CoolerWu3 小时前
TRAE SOLO实战:一个所见即所得的笔记软体
前端·trae
没落英雄3 小时前
简单了解 shadowDom
前端·html
天才熊猫君3 小时前
vue3 基于 el-table 的无限滚动自定义指令实现
前端·javascript
陳陈陳3 小时前
AIGC 时代,用自然语言操作数据库:SQLite + LLM 的轻量级实践
前端·数据库·python
星迷朦龙3 小时前
使用剪贴版复制网页内容
前端