async-eslint
npm i -D eslint eslint-config-async eslint-plugin-node
config
{
"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"],
}
}
no-async-promise-executor
no-await-in-loop
no-promise-executor-return
require-atomic-updates
max-nested-callbacks
no-return-await
prefer-promise-reject-errors
node/handle-callback-err
node/no-callback-literal
node/no-sync
@typescript-eslint/await-thenable
@typescript-eslint/no-floating-promises
@typescript-eslint/no-misused-promises
@typescript-eslint/promise-function-async
详细
eslint-config-async
no-async-promise-executor
不允许将 async 函数传递给 new Promise 构造函数。
js// ❌ new Promise(async (resolve, reject) => {}); // ✅ new Promise((resolve, reject) => {});
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
no-promise-executor-return
不允许在 Promise 构造函数中返回值。
js// ❌ new Promise((resolve, reject) => { return result; }); // ✅ new Promise((resolve, reject) => { resolve(result); });
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);
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 语法。
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 } }
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 数组中。
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)$"]
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 时,才会触发该规则。
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 用户的附加规则
@typescript-eslint/await-thenable
不允许等待不是 Promise 的函数或值。
js// ❌ function getValue() { return someValue; } await getValue(); // ✅ async function getValue() { return someValue; } await getValue();
虽然等待非 Promise 值(它会立即解析)是有效的 JavaScript,但它通常表示程序员错误,例如忘记添加括号来调用返回 Promise 的函数。
@typescript-eslint/no-floating-promises
强制 Promise 附加错误处理程序。
js// ❌ myPromise() .then(() => {}); // ✅ myPromise() .then(() => {}) .catch(() => {});
此规则可防止在代码库中浮动 Promise。浮动 Promise 是没有任何代码来处理潜在错误的 Promise。
始终确保处理拒绝承诺,否则您的 Node.js 服务器将崩溃。
@typescript-eslint/no-misused-promises
不允许将 Promise 传递到不是为处理 Promise 而设计的地方,例如 if 条件。
js// ❌ if (getUserFromDB()) {} // ✅ 👎 if (await getUserFromDB()) {} // ✅ 👍 const user = await getUserFromDB(); if (user) {}
防止您忘记在容易错过的地方等待异步函数。
虽然该规则确实允许在 if 条件中等待,但我建议将结果分配给变量并在条件中使用该变量以提高可读性。
@typescript-eslint/promise-function-async
强制 Promise 返回函数为 async。
js// ❌ function doSomething() { return somePromise; } // ✅ async function doSomething() { return somePromise; }
返回 promise 的非异步函数可能会有问题,因为它可能会引发 Error 对象并返回被拒绝的 promise。通常编写的代码不会处理这两种情况。此规则可确保函数返回被拒绝的承诺或引发错误,但绝不会同时返回两者。
此外,当您知道所有返回 Promise 的函数(因此是异步的)都标记为 async 时,导航代码库会容易得多。