首页
开发技巧正文内容

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

2024年04月21日
阅读时长 3 分钟
阅读量 6
如何像专业人士一样编写异步 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 社区的一员!在您离开之前:

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

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。