Skip to content
大纲

async-eslint

bash
npm i -D eslint eslint-config-async eslint-plugin-node

config

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

// ts
{

  "plugins": [
    "eslint-plugin-node",
    "@typescript-eslint"
  ],
  "extends": [
    "async",
    "async/node",
    "async/typescript"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "tsconfigRootDir": "__dirname",
    "project": ["./tsconfig.json"],
  }
}
  1. no-async-promise-executor
  2. no-await-in-loop
  3. no-promise-executor-return
  4. require-atomic-updates
  5. max-nested-callbacks
  6. no-return-await
  7. prefer-promise-reject-errors
  8. node/handle-callback-err
  9. node/no-callback-literal
  10. node/no-sync
  11. @typescript-eslint/await-thenable
  12. @typescript-eslint/no-floating-promises
  13. @typescript-eslint/no-misused-promises
  14. @typescript-eslint/promise-function-async

详细

eslint-config-async

  1. no-async-promise-executor

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

    js
    // ❌
    new Promise(async (resolve, reject) => {});
    
    // ✅
    new Promise((resolve, reject) => {});
  2. no-await-in-loop

    不允许使用 await 内部循环。

    js
    // ❌
    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

  3. no-promise-executor-return

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

    js
    // ❌
    new Promise((resolve, reject) => {
      return result;
    });
    
    // ✅
    new Promise((resolve, reject) => {
      resolve(result);
    });
  4. require-atomic-updates

    原子性更新,不允许将赋值与 await 结合使用,这可能会导致争用条件。

    比如下面示例,totalPosts 是多少?

    js
    // ❌
    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);

    打印 5 或 3,

    问题在于阅读和更新 totalPosts 之间存在时间差距。这会导致争用条件,这样当在单独的函数调用中更新值时,更新不会反映在当前函数作用域中。因此,这两个函数都将其结果加到 totalPosts 的初始值 0。

    若要避免此争用条件,应确保在更新变量的同时读取变量。

    js
    // ✅
    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);
  5. max-nested-callbacks

    强制执行回调的最大嵌套深度。避免回调地狱

    js
    // ❌
    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 语法。

  6. no-return-await

    不允许不必要的 return await

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

    等待 promise 并立即返回它是不必要的,因为从 async 函数返回的所有值都包装在 promise 中。

    此规则的一个例外是当存在周围的 try...catch 语句时。删除 await 关键字将导致无法捕获拒绝承诺。在这种情况下,我建议您将结果分配给另一行上的变量,以明确意图。

    js
    // 👎
    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
      }
    }
  7. prefer-promise-reject-errors

    在拒绝 Promise 时强制使用 Error 对象。

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

    最佳做法是始终拒绝带有 Error 对象的 Promise。这样做可以更轻松地跟踪错误的来源,因为错误对象存储堆栈跟踪。

eslint-plugin-node

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

  1. node/handle-callback-err

    在回调中强制执行错误处理。

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

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

    可以通过为 .eslintrc 文件中的规则提供第二个参数来更改默认配置: node/handle-callback-err: ["error", "^(e|err|error)$"]

  2. node/no-callback-literal

    强制调用回调函数,Error 并将对象作为第一个参数。如果没有错误,null 或者 undefined 也被接受。

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

    此规则可确保您不会意外调用将非错误作为第一个参数的回调函数。根据错误优先回调约定,回调函数的第一个参数应为 error,如果没有错误,则为 null 或未定义。

    仅当函数被命名为 cb 或 callback 时,才会触发该规则。

  3. node/no-sync

    不允许使用存在异步替代项的 Node.js 核心 API 中的同步方法。

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

    在 Node.js 中使用同步方法进行 I/O 操作会阻止事件循环。在大多数 Web 应用程序中,您希望在执行 I/O 操作时使用异步方法。

TypeScript 用户的附加规则

  1. @typescript-eslint/await-thenable

    不允许等待不是 Promise 的函数或值。

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

    虽然等待非 Promise 值(它会立即解析)是有效的 JavaScript,但它通常表示程序员错误,例如忘记添加括号来调用返回 Promise 的函数。

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

    强制 Promise 附加错误处理程序。

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

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

    始终确保处理拒绝承诺,否则您的 Node.js 服务器将崩溃。

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

    不允许将 Promise 传递到不是为处理 Promise 而设计的地方,例如 if 条件。

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

    防止您忘记在容易错过的地方等待异步函数。

    虽然该规则确实允许在 if 条件中等待,但我建议将结果分配给变量并在条件中使用该变量以提高可读性。

  4. @typescript-eslint/promise-function-async

    强制 Promise 返回函数为 async。

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

    返回 promise 的非异步函数可能会有问题,因为它可能会引发 Error 对象并返回被拒绝的 promise。通常编写的代码不会处理这两种情况。此规则可确保函数返回被拒绝的承诺或引发错误,但绝不会同时返回两者。

    此外,当您知道所有返回 Promise 的函数(因此是异步的)都标记为 async 时,导航代码库会容易得多。