首页
开发技巧正文内容

如何像专业人士一样编写异步 JavaScript 的 11 个最佳实践

2024年04月21日
阅读时长 3 分钟
阅读量 4
如何像专业人士一样编写异步 JavaScript 的 11 个最佳实践

大家好!当你处理不会立即发生的任务时,比如等待文件下载或消息发送时,使用 JavaScript 编写代码可能会很棘手。这就是异步代码。

今天,我将分享 11 个可以帮助你更好地编写这种代码的技巧。而且很酷的是,ESLint 可以帮助确保你遵循这些技巧。你可以选择哪些规则让 ESLint 进行检查。让我们开始,让你的 JavaScript 代码变得更好!

1. no-async-promise-executor

https://eslint.org/docs/latest/rules/no-async-promise-executor#rule-details

不建议将 async 函数传递给 new Promise 构造函数。

// ❌
const foo = new Promise(async (resolve, reject) => {
  readFile('foo.txt', function(err, result) {
    if (err) {
      reject(err);
    } else {
      resolve(result);
    }
  });
});

// ✅
const foo = new Promise((resolve, reject) => {
  readFile('foo.txt', function(err, result) {
    if (err) {
      reject(err);
    } else {
      resolve(result);
    }
  });
});

首先,如果在 Promise 的构造函数中使用 async,可能不需要包装一个 Promise。此外,如果 async 函数抛出错误,新创建的 Promise 不会被拒绝。这意味着你无法捕获该错误。

2. no-await-in-loop

https://eslint.org/docs/latest/rules/no-await-in-loop#rule-details

不应在循环中使用 await。这通常意味着程序没有充分利用 JavaScript 的事件驱动特性。

最好同时运行这些异步任务。这样可以使代码运行得更快。

// ❌
async function foo(things) {
  const results = [];
  for (const thing of things) {
    // 不好:每次循环迭代都会延迟,直到整个异步操作完成
    results.push(await bar(thing));
  }
  return baz(results);
}

// ✅
async function foo(things) {
  const results = [];
  for (const thing of things) {
    // 好:所有异步操作立即启动。
    results.push(bar(thing));
  }
  // 现在,所有异步操作都在运行,我们在这里等待它们全部完成。
  return baz(await Promise.all(results));
}

3. no-promise-executor-return

https://eslint.org/docs/latest/rules/no-promise-executor-return#rule-details

不要在 Promise 构造函数中返回值。从那里返回的值是无用的。它们也不会影响 Promise 的状态。正确的方法是将值传递给 resolve。如果有错误,将其传递给 reject

// ❌
new Promise((resolve, reject) => {
    if (someCondition) {
        return defaultResult;
    }
    getSomething((err, result) => {
        if (err) {
            reject(err);
        } else {
            resolve(result);
        }
    });
});

// ✅
new Promise((resolve, reject) => {
    if (someCondition) {
        resolve(defaultResult);
        return;
    }
    getSomething((err, result) => {
        if (err) {
            reject(err);
        } else {
            resolve(result);
        }
    });
});

4. require-atomic-updates

https://eslint.org/docs/latest/rules/require-atomic-updates#rule-details

不要将赋值与 await 混合使用。这可能导致竞态条件。

看看这段代码。你认为最后 totalPosts 会是什么?

// ❌
let totalPosts = 0;

async function getPosts(userId) {
  const users = [{ id: 1, posts: 5 }, { id: 2, posts: 3 }];
  await sleep(Math.random() * 1000);
  return users.find((user) => user.id === userId).posts;
}

async function addPosts(userId) {
  totalPosts += await getPosts(userId);
}

await Promise.all([addPosts(1), addPosts(2)]);
console.log('Post count:', totalPosts);

totalPosts 可能会打印 3 或 5,而不是 8。你可以在浏览器中尝试一下。

问题在于读取 totalPosts 和更新它之间的间隙。这导致了竞态条件。当值在单独的函数调用中更新时,更新不会反映在当前函数范围内。因此,两个函数都将其结果添加到初始值为 0 的 totalPosts 中。

以下是避免竞态条件的方法:

// ✅
let totalPosts = 0;

async function getPosts(userId) {
  const users = [{ id: 1, posts: 5 }, { id: 2, posts: 3 }];
  await sleep(Math.random() * 1000);
  return users.find((user) => user.id === userId).posts;
}

async function addPosts(userId) {
  const posts = await getPosts(userId);
  totalPosts += posts; // 变量被读取并立即更新
}

await Promise.all([addPosts(1), addPosts(2)]);
console.log('Post count:', totalPosts);

5. max-nested-callbacks

https://eslint.org/docs/latest/rules/max-nested-callbacks#rule-details

为了避免回调地狱和深层嵌套:

// ❌
async1((err, result1) => {
  async2(result1, (err, result2) => {
    async3(result2, (err, result3) => {
      async4(result3, (err, result4) => {
        console.log(result4);
      });
    });
  });
});

// ✅
const result1 = await asyncPromise1();
const result2 = await asyncPromise2(result1);
const result3 = await asyncPromise3(result2);
const result4 = await asyncPromise4(result3);
console.log(result4);

回调地狱使代码难以阅读和维护。最好将回调重构为 Promise,并使用现代的 async/await 语法。

6. prefer-promise-reject-errors

https://eslint.org/docs/latest/rules/prefer-promise-reject-errors#rule-details

在拒绝 Promise 时,建议始终使用 Error 对象。这样可以更容易地跟踪错误堆栈。

// ❌
Promise.reject('An error occurred');

// ✅
Promise.reject(new Error('An error occurred'));

7. node/handle-callback-err

https://eslint.org/docs/latest/rules/handle-callback-err

始终在 Node.js 异步回调中处理异常。

// ❌
function callback(err, data) {
  console.log(data);
}

// ✅
function callback(err, data) {
  if (err) {
    console.log(err);
    return;
  }

  console.log(data);
}

在 Node.js 中,异常通常作为回调的第一个参数传递。忘记处理这些异常可能会导致应用程序中的不可预测问题。

如果函数的第一个参数命名为 err,则会触发此规则。你也可以在 .eslintrc 文件中自定义异常参数的名称。

8. node/no-sync

https://eslint.org/docs/latest/rules/no-sync

在 Node.js 核心 API 中不建议使用同步方法,而有异步的替代方法时。

// ❌
const file = fs.readFileSync(path);

// ✅
const file = await fs.readFile(path);

在 Node.js 中使用同步方法进行 I/O 操作会阻塞事件循环。在大多数情况下,使用异步方法进行 I/O 是更好的选择。

9. @typescript-eslint/await-thenable

https://typescript-eslint.io/rules/await-thenable/

不建议 await 非 Promise 函数或值。

// ❌
function getValue() {
  return someValue;
}

await getValue();

// ✅
async function getValue() {
  return someValue;
}

await getValue();

10. @typescript-eslint/no-floating-promises

https://typescript-eslint.io/rules/no-floating-promises

建议将异常处理代码附加到 Promises 上。

// ❌
myPromise()
  .then(() => {});

// ✅
myPromise()
  .then(() => {})
  .catch(() => {});

养成一个好习惯,始终准备好处理异常!

11. @typescript-eslint/no-misused-promises

https://typescript-eslint.io/rules/no-misused-promises

不建议将 Promises 传递给不处理它们的地方,比如 if 条件。

// ❌
if (getUserFromDB()) {}

// ✅ 👎 
if (await getUserFromDB()) {}

最好提取一个变量以提高代码的可读性。

// ✅ 👍 
const user = await getUserFromDB();
if (user) {}

记住,编写干净高效的代码很重要。这不仅仅是让它工作,还要让它工作得好,并且易于他人理解。继续练习这些良好的习惯,愉快地编码吧!


每当 Nebula Nomad 发布新文章时,都会收到一封电子邮件。

用简单的语言解释 🚀

感谢您成为 In Plain English 社区的一员!在您离开之前:

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

使用 Next.js App Router 进行本地化
2024年04月22日23:11

使用 Next.js App Router 进行本地化

使用新的 App Router 为 Next.js 应用程序进行本地化和国际化。App Router 在名为 app 的目录中工作,可以与 Pages Router 一起使用。使用动态路由来创建根据动态数据创建的路由。使用中间件来实现本地化和语言切换。
如何像专业人士一样编写异步 JavaScript 的 11 个最佳实践
2024年04月21日04:04

如何像专业人士一样编写异步 JavaScript 的 11 个最佳实践

当处理不会立即发生的任务时,使用 JavaScript 编写异步代码可能会很棘手。本文分享了11个可以帮助你更好地编写异步 JavaScript 代码的最佳实践,包括避免在循环中使用await、不在Promise构造函数中返回值等。这些实践可以提高代码的性能和可读性,让你的JavaScript代码更加专业。
UI 设计师需要了解 Flexbox 和 CSS Grid
2024年04月08日08:11

UI 设计师需要了解 Flexbox 和 CSS Grid

大多数设计师都熟悉响应式设计,但现代 CSS 布局可以提供更灵活和动态的设计。本文介绍了响应式基于列的设计与现代 CSS 布局、作为设计师理解 CSS Flexbox 和 CSS Grid、以及断点的使用。CSS Grid 是一种强大的布局工具,可以创建复杂的布局结构,而 Flexbox 则适用于组件级别的布局。设计师可以根据具体情况选择使用 Flexbox、CSS Grid 或两者结合。
10款提升Web开发速度的强大工具
2024年04月07日02:13

10款提升Web开发速度的强大工具

10款提升Web开发速度的强大工具,覆盖了页面动效,前端框架,颜色选择,背景移除,Icon等各个方面。
Android Compose实战:开发动态银行卡UI
2024年03月19日21:19

Android Compose实战:开发动态银行卡UI

本教程介绍了如何使用Jetpack Compose构建一个漂亮的数字银行卡UI。通过使用Canvas和组合函数,您可以创建一个灵活且可自定义的银行卡UI,包括卡号、持卡人信息和卡类型。这个教程还介绍了如何使用Modifier参数和外部文件来增加灵活性和可重用性。
完美的Next.js点赞按钮
2024年03月17日10:22

完美的Next.js点赞按钮

在Next.js中实现一个完美的点赞按钮并不容易。但是通过结合tRPC内置的乐观更新功能和lodash的防抖能力,我解决了这些问题,打造了完美的Next.js点赞按钮,今天我将向您展示如何做到这一点。本文介绍了认证、tRPC、后端和前端的实现步骤,并提供了相关代码示例。通过这篇文章,您可以学习如何在Next.js应用程序中实现一个功能强大的点赞按钮。