7 个技巧助你写出优雅高效的 JavaScript 异步代码

272 阅读4分钟

在 JavaScript 开发中,异步编程是绕不开的。无论是网络请求、读取文件,还是执行其他耗时操作,异步代码的质量直接影响着程序的性能和稳定性。今天,就来和大家分享 7 个编写高质量 JavaScript 异步代码的实用技巧,让你的代码告别混乱,更加优雅高效!

一、reject 一个 Error 对象

在处理 Promise 时,当我们需要拒绝一个 Promise 时,强制使用 Error 对象是个非常好的习惯。这么做的原因在于,浏览器底层在分配堆栈和查找堆栈时,Error 对象能让其工作更轻松,方便我们快速定位错误。

// bad
Promise.reject('error reason');

// good
Promise.reject(new Error('error reason'))

上述代码中,“bad” 的写法直接传递字符串作为拒绝原因,在复杂的项目中,这种方式不利于错误的排查和处理;而 “good” 的写法创建了一个 Error 对象,包含了更丰富的错误信息,便于开发者定位问题所在。

二、不要在 Promise 上使用 async

将 async 函数传递给 Promise 构造函数不仅多此一举,还存在潜在问题。如果 async 函数内部出现异常,外部的 Promise 并不会按照预期 reject 。

// bad
new Promise(async (resolve, reject) => {})

// good
new Promise((resolve, reject) => {
  async function() {
  // 具体的代码逻辑
  }()
})

“bad” 示例中,直接将 async 函数作为 Promise 构造函数的参数,这种写法会导致异常处理出现问题;“good” 示例则将 async 函数放在 Promise 构造函数内部执行,避免了不必要的问题。

三、不要使用 await 在循环中

在循环中使用 await 会使异步任务依次执行,大大降低了代码的执行效率。我们可以将这些异步任务改为并发执行,利用 Promise.all 方法大幅提升效率。

// bad
const paths = ['path1', 'path2', 'path3'];
for(const apiPath of paths) {
const { data } = await request(apiPath)
}

// good
const paths = ['path1', 'path2', 'path3'];
const results = [];
for(const apiPath of paths) {
const res = request(apiPath);
results.push(res);
}
await Promise.all(results);

“bad” 示例中,每次循环都要等待上一个异步请求完成,导致整体执行时间变长;“good” 示例先将所有请求放入数组,再通过 Promise.all 并发执行,显著提高了效率。

四、不要再 Promise 中使用 return 语句

在 Promise 构造函数中使用 return 语句,不符合 Promise 的使用规范,也不利于代码的可读性和维护性。正确的做法是使用 resolve 和 reject 来处理结果和错误。

// bad
new Promise((resolve, reject) => {
    if(isOK) return 'ok'
    return 'not ok'
})

// good
new Promise((resolve, reject) => {
    if(isOK) resolve('ok')
    reject(new Error('not ok'))
})

“bad” 示例中使用 return 语句,会让代码逻辑变得混乱;“good” 示例遵循 Promise 的规范,清晰地处理成功和失败的情况。

五、防止回调地狱

回调地狱是异步编程中常见的问题,多层嵌套的回调函数不仅难以阅读,而且维护起来十分困难。利用 async/await 可以让代码变得更加简洁直观。

// bad
p1((err, res1) => {
    p2(res1, (err, res2) => {
        p3(res2, (err, res3) => {
            p4(res3, (err, res4) => {
                console.log(res4)
            })
        })
    })
})

// good
const res1 = await p1()
const res2 = await p1(res1)
const res3 = await p1(res2)
const res4 = await p1(res3)
console.log(res4)

“bad” 示例中,层层嵌套的回调函数就像 “回调金字塔”,难以理解;“good” 示例使用 async/await 让代码以同步的方式书写,逻辑更加清晰。

六、别忘了异常处理

无论是使用 Promise 的 then/catch 还是 async/await ,都不能忽略异常处理,否则一旦出现错误,程序可能会崩溃。

// bad(Promise 示例)
asyncPromise().then(() => {})

// good(Promise 示例)
asyncPromise().then(() => {}).catch(() => {})

// bad(async/await 示例)
const result = await asyncPromise()

// good(async/await 示例)
try {
    const result = await asyncPrmise()
} catch() {
    // 进行错误处理
}

“bad” 示例中没有处理异常,程序的稳定性无法保证;“good” 示例通过 catch 或 try/catch 捕获异常,增强了程序的健壮性。

七、不要 await 一个非 Promise 函数

await 只能用于等待 Promise 对象,如果用于非 Promise 函数,会导致代码出现错误。我们需要将函数改造为返回 Promise 的形式。

function getUserInfo() {
    return userInfo
}
// bad
await getUserInfo()

// good
async function getUserInfo() {
    return userInfo
}
await getUserInfo()

“bad” 示例中直接对非 Promise 函数使用 await ,会引发错误;“good” 示例将函数改为 async 函数,使其返回 Promise ,符合 await 的使用要求。

掌握这些编写高质量 JavaScript 异步代码的技巧,能让我们在开发过程中写出更规范、高效且易于维护的代码。希望大家在实际项目中多多实践,不断提升自己的编程水平!如果大家还有其他关于异步编程的小技巧,欢迎在评论区分享交流~