在实际项目中,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
最早的异步解决方案,通过函数参数传递回调:
// Callback 方式
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})
2. Promise
ES6 引入,解决回调地狱问题,提供链式调用:
// Promise 方式
fetch('/api/data')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error))
3. Generator
ES6 同期引入的特性,配合 Promise 可以实现类似同步的异步代码:
// Generator 方式(需要执行器运行)
function* fetchData() {
const response = yield fetch('/api/data')
const data = yield response.json()
return data
}
但 Generator 的本意是生成值序列(Iterator),不是为异步设计的。
4. async/await
ES2017 引入,专为异步设计的语法糖:
// 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):多层嵌套让代码变成"圣诞树",难读又难改
-
错误处理繁琐:每个回调都要单独处理错误,稍不留神就漏掉了
-
无法取消:操作一旦启动就停不下来
示例对比:
// ❌ 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 必须传回调函数
- 数组方法:
map、filter、forEach 也需要回调
如何避免使用 Callback?
方案 1:Promise 包装遗留 API
// 遗留的 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 替代原生定时器
原生的 setTimeout 和 setInterval 也是基于 Callback 的,而且取消起来很麻烦。signal-timers 这个库可以让定时器返回 Promise,还支持 AbortSignal:
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 是专门为异步设计的,何必舍近求远?
反例和正例对比:
// ❌ 禁止:用 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:
// 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 回归本职工作——生成值序列,这才是它的正确打开方式:
// ✅ 正确的 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.all、Promise.race 这些组合器很好用
-
可取消:配合 AbortSignal 可以随时取消操作
标准的异步函数模式:
// ✅ 标准异步函数模式
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
}
}
统一的错误处理:
// ✅ 统一错误处理
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
}
}
并行操作:
// ✅ 使用 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 处理。它们就这么"漂"在代码里,出了问题也没人知道。
// ❌ 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 没有被 await、return 或使用 .then()/.catch() 处理,就是 Floating Promise。
Floating Promise 的危害
Floating Promise 看起来无害,实际上会带来不少麻烦。
1. 错误被忽略,导致静默失败
最常见的问题就是错误被"吞"了,用户以为操作成功了,其实早就失败了。
// ❌ 问题:保存失败但没有任何提示
async function saveUser(user) {
database.save(user) // 如果保存失败,错误被完全忽略
showSuccessMessage('保存成功')
}
// 实际运行:
// - database.save() 返回 Promise 但没有等待
// - 如果 Promise reject,错误不会被捕获
// - 用户看到"保存成功",但数据可能保存失败
2. 时序不确定,导致竞态条件
// ❌ 问题:通知可能在更新完成前发送
async function updateAndNotify(userId, data) {
updateUser(userId, data) // 没有 await
sendNotification(userId, '更新成功') // 立即执行
}
// 执行顺序:
// 1. updateUser() 开始执行(返回 Promise)
// 2. sendNotification() 立即执行
// 3. updateUser() 可能还在进行中
// 结果:用户收到"更新成功",但数据还未更新
3. 难以调试,调用栈丢失
// ❌ 问题:错误发生时难以追溯来源
function processData() {
fetchData().then((data) => {
throw new Error('处理失败')
})
// 没有 .catch(),错误不会被捕获
}
// 控制台输出:
// Uncaught (in promise) Error: 处理失败
// at <anonymous>
//
// 问题:调用栈没有显示是在 processData() 中出错
4. 资源泄漏
// ❌ 问题:未处理的 Promise 可能持有资源引用
function startPolling() {
setInterval(() => {
fetchData() // 每次都创建新的 Floating Promise
.then((data) => updateUI(data))
// 如果没有 .catch(),错误会累积
}, 1000)
}
// 问题:
// - 每秒创建新的 Promise,但错误没有被处理
// - 可能导致内存泄漏
// - 错误累积可能影响性能
如何禁止 Floating Promise
@typescript-eslint/no-floating-promises 这个规则专门用来检测 Floating Promise。它要求所有 Promise 必须用以下三种方式之一处理:
-
用
await 等待 - 等它完成再继续
-
用
return 返回 - 让调用方去处理
-
用
.then()/.catch() 或 .finally() 处理 - 链式处理结果和错误
配置规则
// eslint.config.js
export default [
{
rules: {
'@typescript-eslint/no-floating-promises': [
'error',
{
ignoreVoid: false, // 禁止使用 void 标记 Promise
},
],
},
},
]
代码示例
// ❌ 错误:直接调用异步函数,没有处理返回的 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 都属于这一类。
// 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 的时候,代码会暂停在那里,等操作完成才往下走。
// 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?
// ❌ 代码意图不清晰:是忘记 await 了,还是有意不等待?
function handleClick() {
fetch('/api/track', {
method: 'POST',
body: JSON.stringify({ action: 'click' }),
})
// 继续执行其他逻辑
navigateToNextPage()
}
明确标记可以解决这个问题:
-
区分有意和疏忽:告诉审查者,我是故意不等的
-
统一错误管理:集中处理 Fire-and-Forget 的错误
-
提高可读性:一眼就知道是咋回事
实现方式:fireAndForget 工具函数
/**
* 明确标记 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. 内存泄漏和资源浪费
// ❌ 问题:轮询没有被取消
async function startPolling() {
while (true) {
const data = await fetchData()
updateUI(data)
await sleep(5000)
}
}
// 用户离开页面后,startPolling 仍在运行
// 持续消耗网络和内存资源
2. 状态不一致和竞态条件
// ❌ 问题:快速切换时,旧请求可能后完成
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. 执行无效操作
// ❌ 问题:组件卸载后仍在执行
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 之后检查一下要不要取消
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:取消操作是正常行为,没必要上报到监控系统:
function reportError(error) {
// 忽略 AbortError
if (error.name === 'AbortError') {
return
}
// 上报业务错误
errorMonitoring.report(error)
}
try-catch 中直接返回:碰到 AbortError 就直接返回,不走错误处理流程:
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:
// 全局 Promise 错误处理
window.addEventListener('unhandledrejection', (event) => {
if (event.reason?.name === 'AbortError') {
// 忽略 AbortError
event.preventDefault()
return
}
// 处理其他错误
reportError(event.reason)
})
fireAndForget 中忽略 AbortError: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)
})
}
异步编程的规范
除了上面提到的实践,异步编程里还有一些常见的坑需要注意。这一节介绍几个实用的 Lint 规则,它们能帮你避开 Promise 误用、不必要的包装、callback 和 Promise 混用等问题,让异步代码写得更规范。
no-misused-promises (eslint)
规则说明: 禁止在不该用 Promise 的地方用 Promise。
为什么需要这个规则?
Promise 是异步的,在需要同步值的地方用它会出问题:
// ❌ 错误:条件表达式中使用 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
})
正确做法:
// ✅ 正确:await 后再使用
const data = await fetchData()
if (data) {
// 使用数据
}
// ✅ 正确:await 后再进行逻辑运算
const data = (await fetchData()) || defaultValue
// ✅ 正确:不返回值,或返回 void
button.addEventListener('click', async () => {
await handleClick()
// 不返回值
})
配置:
// eslint.config.js
export default [
{
rules: {
'@typescript-eslint/no-misused-promises': 'error',
},
},
]
no-return-wrap (oxlint)
规则说明: 禁止多余的 Promise 包装。
为什么需要这个规则?
async 函数会自动把返回值包装成 Promise,你再手动包一层就多余了:
// ❌ 错误:多余的 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 就好了:
// ❌ 错误:在 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 写起来更简洁,错误处理也更统一:
// ❌ 不推荐:使用 .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 对象会保留堆栈信息,调试的时候能快速定位问题:
// ❌ 错误: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 关键字就白加了,还不如删掉:
// ❌ 错误: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
}