帮助编写异步代码的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 構造函數中的嵌套回調內返回值。請務必使用
resolve或reject來結束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 時,就會觸發該規則。在大型項目中,經常會發現不同的錯誤命名方式,如 e 或 error。你可以通過在 .eslintrc 文件中為規則提供第二個參數來更改默認配置:node/handle-callback-err: ["error", "^(e|err|error)$"]。
node/no-callback-literal
該規則強制要求在調用回調函數時將 Error 對象作為第一個參數。如果沒有錯誤,也接受 null 或 undefined。
// ?
cb('An error!');
callback(result);
// ?
cb(new Error('An error!'));
callback(null, result);
該規則可確保你不會意外調用第一個參數為非錯誤的回調函數。根據錯誤優先的回調約定,回調函數的第一個參數應該是錯誤,如果沒有錯誤,則應該是 null 或 undefined 。
只有當函數名為 cb 或 callback 時,才會觸發該規則。
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!?? ??
以上就是本文的全部內容,如果對你有所幫助,歡迎點贊、收藏、轉發~
總結
以上是生活随笔為你收集整理的帮助编写异步代码的ESLint规则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大模型应用(1) 搭建本地知识库
- 下一篇: STM32CubeMX教程20 SPI