• 帮助编写异步代码的ESLint规则


    调试 JavaScript 中的异步代码有时就像在雷区中穿梭。你不知道 console.log 会在何时何地打印出来,也不知道代码是如何执行的。

    你很难正确构造异步代码,使其按照你的意图以正确的顺序执行。

    如果在编写异步代码时能得到一些指导,并在即将出错时收到一条有用的信息,那岂不更好?

    幸运的是,在将错误推向生产环境之前,我们有一些规则来捕捉这些错误。以下是一份经过编译的linting规则列表,可为你在 JavaScript 和 Node.js 中编写异步代码提供具体帮助。

    即使你最终没有在项目中使用这些规则,阅读它们的说明也会让你更好地理解异步代码,并提高你的开发技能。

    ESLint异步代码规则

    ESLint 默认提供以下规则。将它们添加到 .eslintrc 配置文件中即可启用。

    no-async-promise-executor

    该规则不允许将async函数传递给new Promise构造函数。

    // ❌
    new Promise(async (resolve, reject) => {});
    
    // ✅
    new Promise((resolve, reject) => {});
    

    虽然从技术上讲,向 Promise 构造函数传递异步函数是有效的,但出于以下两个原因,这样做通常是错误的。首先,如果异步函数抛出错误,错误将丢失,不会被新构造的 Promise 拒绝。其次,如果在构造函数内部使用了 await,那么外层的 Promise 可能就没有必要了,可以将其删除。

    no-await-in-loop

    该规则不允许在循环内使用await

    在对可迭代对象的每个元素进行操作并等待异步任务时,往往表明程序没有充分利用 JavaScript 的事件驱动架构。通过并行执行任务,可以大大提高代码的效率。

    // ❌
    for (const url of urls) {
      const response = await fetch(url);
    }
    
    // ✅
    const responses = [];
    for (const url of urls) {
      const response = fetch(url);
      responses.push(response);
    }
    
    await Promise.all(responses);
    

    如果你想按顺序运行任务,我建议你使用行内注释暂时禁用该规则:// eslint-disable-line no-await-in-loop

    no-promise-executor-return

    该规则不允许在 Promise 构造函数中返回值。

    // ❌
    new Promise((resolve, reject) => {
      return result;
    });
    
    // ✅
    new Promise((resolve, reject) => {
      resolve(result);
    });
    

    在 Promise 构造函数中返回的值不能使用,也不会对 promise 产生任何影响。应将该值传递给resolve,如果发生错误,则调用 reject 并告知错误信息。

    该规则不会阻止你在 Promise 构造函数中的嵌套回调内返回值。请务必使用 resolvereject 来结束promise。

    require-atomic-updates

    该规则不允许将赋值与 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);
    

    也许你已经感觉到这是一个骗人的问题,答案不是 8。没错,totalPosts 打印的是 5 或 3。自己在浏览器中试试吧。

    问题在于读取和更新 totalPosts 之间存在时间差。这就造成了一个竞赛条件,当值在单独的函数调用中更新时,更新不会反映在当前函数的作用域中。因此,这两个函数都将其结果添加到 totalPosts 的初始值 0 中。

    要避免这种竞赛条件,应确保在更新变量的同时读取变量。

    // ✅
    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; // variable is read and immediately updated
    }
    
    await Promise.all([addPosts(1), addPosts(2)]);
    console.log('Post count:', totalPosts);
    

    max-nested-callbacks

    该规则强制限制回调的最大嵌套深度。换句话说,该规则可防止回调地狱!

    /* eslint max-nested-callbacks: ["error", 3] */
    
    // ❌
    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);
    

    深度嵌套会使代码难以阅读,更难以维护。在编写 JavaScript 异步代码时,将回调重构为promise,并使用现代的 async/await 语法。

    no-return-await

    该规则不允许不必要的return await

    // ❌
    async () => {
      return await getUser(userId);
    }
    
    // ✅
    async () => {
      return getUser(userId);
    }
    

    由于async函数返回的所有值都已封装在 promise 中,因此等待 promise 并立即返回是不必要的。因此,你可以直接返回 promise。

    当周围有 try...catch 语句时,这条规则会出现例外。移除 await 关键字会导致不捕获拒绝的promise。在这种情况下,我建议你将结果赋值给另一行的变量,以明确意图。

    // 👎
    async () => {
      try {
        return await getUser(userId);
      } catch (error) {
        // Handle getUser error
      }
    }
    
    // 👍
    async () => {
      try {
        const user = await getUser(userId);
        return user;
      } catch (error) {
        // Handle getUser error
      }
    }
    

    prefer-promise-reject-errors

    该规则强制要求在拒绝 Promise 时使用 Error 对象。

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

    最佳做法是始终使用 Error 对象来拒绝Promise。因为错误对象会存储堆栈跟踪,所以这样做可以更容易地跟踪错误的来源。

    Node.js 特定规则

    以下规则是 esLint-plugin-node 插件为 Node.js 提供的附加 ESLint 规则。要使用这些规则,需要安装该插件并将其添加到 .eslintrc 配置文件的 plugins 数组中。

    node/handle-callback-err

    该规则强制在回调中处理错误。

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

    在 Node.js 中,将错误作为第一个参数传递给回调函数是很常见的。忘记处理错误会导致应用程序行为异常。

    当函数的第一个参数名为 err 时,就会触发该规则。在大型项目中,经常会发现不同的错误命名方式,如 eerror。你可以通过在 .eslintrc 文件中为规则提供第二个参数来更改默认配置:node/handle-callback-err: ["error", "^(e|err|error)$"]

    node/no-callback-literal

    该规则强制要求在调用回调函数时将 Error 对象作为第一个参数。如果没有错误,也接受 nullundefined

    // ❌
    cb('An error!');
    callback(result);
    
    // ✅
    cb(new Error('An error!'));
    callback(null, result);
    

    该规则可确保你不会意外调用第一个参数为非错误的回调函数。根据错误优先的回调约定,回调函数的第一个参数应该是错误,如果没有错误,则应该是 nullundefined

    只有当函数名为 cbcallback 时,才会触发该规则。

    node/no-sync

    如果 Node.js 核心 API 中存在异步替代方法,则该规则不允许使用同步方法。

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

    在 Node.js 中使用同步方法进行 I/O 操作会阻止事件循环。在大多数网络应用程序中,进行 I/O 操作时需要使用异步方法。

    在 CLI 实用程序或脚本等某些应用程序中,使用同步方法也是可以的。你可以使用 /* eslint-disable node/no-sync */ 在文件顶部禁用这一规则。

    针对 TypeScript 用户的附加规则

    如果你的项目使用的是 TypeScript,那么你可能已经熟悉了 TypeScript ESLint(以前的 TSLint)。以下规则仅适用于 TypeScript 项目,因为它们会从类型信息中推断出额外的上下文。

    @typescript-eslint/await-thenable

    该规则不允许等待非 Promise 的函数或值。

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

    虽然等待一个非 Promise 的值是有效的 JavaScript(它会立即解析),但这往往表明程序员出错了,比如在调用一个返回 Promise 的函数时忘记加上括号。

    @typescript-eslint/no-floating-promises

    此规则强制 Promise 必须附加错误处理程序。

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

    此规则可防止代码库中出现浮动 Promise。浮动 Promise 是指没有任何代码来处理潜在错误的 Promise。

    请务必处理 Promise 拒绝,否则你的 Node.js 服务器将会崩溃。

    @typescript-eslint/no-misused-promises

    该规则禁止将 Promise 传递到非处理 Promise 的地方,如 if 条件语句。

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

    该规则可防止你在容易遗漏的地方忘记 await 异步函数。

    虽然该规则允许在 if 条件语句中等待,但我建议将结果赋值给一个变量,然后在条件中使用该变量,以提高可读性。

    @typescript-eslint/promise-function-async

    该规则强制 Promise 返回函数必须是 async

    // ❌
    function doSomething() {
      return somePromise;
    }
    
    // ✅
    async function doSomething() {
      return somePromise;
    }
    

    返回promise的非同步函数可能会有问题,因为它可能会抛出一个 Error 对象并返回一个被拒绝的promise。代码通常不会同时处理这两种情况。本规则可确保函数返回被拒绝的promise或抛出 Error,但绝不会同时返回两种情况。

    此外,如果知道所有返回 Promise 的函数都被标记为 async ,那么浏览代码库就容易多了。

    启用这些规则

    我发布了一个 ESLint 配置包,你可以轻松将其添加到你的项目中。它分别导出了基本规则、Node.js 特定规则和 TypeScript 特定规则。

    针对非TypeScript用户

    npm install --save-dev eslint eslint-config-async eslint-plugin-node
    

    然后在你的 .eslintrc 配置文件中添加下列配置:

    {
      "plugins": [
        "eslint-plugin-node"
      ],
      "extends": [
        "async",
        "async/node"
      ]
    }
    

    针对TypeScript用户

    安装包及其依赖:

    npm install --save-dev eslint eslint-config-async eslint-plugin-node typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
    

    然后在你的 .eslintrc 配置文件中添加下列配置:

    "plugins": [
      "eslint-plugin-node",
      "@typescript-eslint"
    ],
    "extends": [
      "async",
      "async/node",
      "async/typescript"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
      "tsconfigRootDir": "__dirname",
      "project": ["./tsconfig.json"],
    };
    

    就是这样!将这些异步代码的校验规则添加到你的项目中,并修复出现的任何问题。你可能会发现一两个 bug!🐛 🚫

    以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~

  • 相关阅读:
    tomcat7以上,设置maxPostSize=“0“参数后台获取不到的问题
    [NPUCTF2020]ezinclude
    编译原理—中间代码生成、布尔表达式翻译、短路计算、控制流语句翻译、条件语句、循环语句
    小红书母婴行业文案怎么写,创作方向有哪些?
    NET Framework合集
    ZXing - barcode scanning library for Java, Android
    [MapStruct]如何获取Mapper
    Android Media Framework(三)OpenMAX API阅读与分析
    【网站架构】服务器弹性伸缩不能全自动,实际如何追加服务器
    详解如何在python中实现简单的app自动化框架
  • 原文地址:https://www.cnblogs.com/chuckQu/p/17978582