帮助编写异步代码的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规则的更多相关文章
- 使用 Vert.X Future/Promise 编写异步代码
Future 和 Promise 是 Vert.X 4.0中的重要角色,贯穿了整个 Vert.X 框架.掌握 Future/Promise 的用法,是用好 Vert.X.编写高质量异步代码的基础.本文 ...
- TypeScript 异步代码类型技巧
在typescript下编写异步代码,会遇到难以自动识别异步返回值类型的情况,本文介绍一些技巧,以辅助编写更健全的异步代码. callback 以读取文件为例: readFile是一个异步函数,包含p ...
- .NET Core学习笔记(4)——谨慎混合同步和异步代码
原则上我们应该避免编写混合同步和异步的代码,这其中最大的问题就是很容易出现死锁.让我们来看下面的例子: private void ButtonDelayBlock_Click(object sende ...
- [技术翻译]在现代JavaScript中编写异步任务
本周再来翻译一些技术文章,本次预计翻译三篇文章如下: 04.[译]使用Nuxt生成静态网站(Generate Static Websites with Nuxt) 05.[译]Web网页内容是如何影响 ...
- 如何编写优雅的异步代码 — CompletableFuture
前言 在我们的意识里,同步执行的程序都比较符合人们的思维方式,而异步的东西通常都不好处理.在异步计算的情况下,以回调表示的动作往往会分散在代码中,也可能相互嵌套在内部,如果需要处理其中一个步骤中可能发 ...
- ESLint 规则
ESLint由 JavaScript 红宝书 作者 Nicholas C.Zakas 编写, 2013 年发布第一个版本. ESLint是一个以可扩展.每条规则独立的,被设计为完全可配置的lint工具 ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】
原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...
- node.js的作用、回调、同步异步代码、事件循环
http://www.nodeclass.com/articles/39274 一.node.js的作用 I/O的意义,(I/O是输入/输出的简写,如:键盘敲入文本,输入,屏幕上看到文本显示输出.鼠标 ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!
原文地址 迁移到:http://www.bdata-cap.com/newsinfo/1741515.html 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和f ...
- co.js - 让异步代码同步化
近期在全力开发个人网站,并且又沉淀了一些前后端的技术.近期会频繁更新. 这篇文章首发于我的个人网站:听说 - https://tasaid.com/,建议在我的个人网站阅读,拥有更好的阅读体验. 这篇 ...
随机推荐
- 【笔记整理】xpath复习
又是xpath.... 10年前学java就学过了...哈哈哈 要就看w3cschool的教程就行了, 函数部分会用得到: https://www.w3school.com.cn/xpath/xpat ...
- 3D网站LOGO动画
相关技术和实现分析 3D模型 帧动画 threejs 推荐用blender创建3d模型,k帧实现从上到下翻转的帧动画 threejs 中执行帧动画,并关联滚动条 threejs 模型材质 Blende ...
- Educational Codeforces Round 159 总结
最失败的一集. C 开题顺序搞错,不小心先开了C,以为是A.还好C不难. 题意大概是在给定的数组最后添一个数(所有数两两不同),再自定义一个数 \(k\) ,数组中每个数可以加上若干个 \(k\) , ...
- Linux的发行版及其描述
Linux的十种发行版及其描述 linux其中十种发行版本为Debian.Gentoo.Ubuntu.Damn Vulnerable Linux.红帽企业级Linux. CentOS.Fedora.K ...
- springboot-jpa获取session
springboot获取hibernate的session进行更精细的SQL操作,默认的jpa并不能满足一些复杂需求(可能是我把需求设计复杂了) 通过查看JpaRepository的底层实现,发现是通 ...
- 【Python】【OpenCV】OCR识别(二)——透视变换
对于OCR技术在处理有角度有偏差的图像时是比较困难的,而水平的图像使用OCR识别准确度会高很多,因为文本通常是水平排列的,而OCR算法一般会假设文本是水平的. 针对上述情况,所以我们在处理有角度的图象 ...
- 文心一言 VS 讯飞星火 VS chatgpt (22)-- 算法导论4.2 2题
二.为Strassen算法编写伪代码. 文心一言: Strassen算法是一种用于矩阵乘法的算法,它的核心思想是通过不断将原始矩阵分割成较小的子矩阵来实现高效的乘法运算.以下是Strassen算法的伪 ...
- 云图说|应用编排服务AOS,助力应用上云自动化
如今,企业想要扩大业务.进行创新,上云已经成为了必经之路.应用编排服务AOS为企业提供应用上云的自动化能力,支持编排华为云上的主流云服务,将复杂的云服务资源配置和应用部署配置通过模板描述,从而实现在华 ...
- GaussDB CN服务异常实例分析
摘要:先通过OPS确认节点状态是否已经恢复,或登录后台执行cm_ctl query -Cv确认集群是否已经Normal. 本文分享自华为云社区<[实例状态]GaussDB CN服务异常>, ...
- 推理实践丨如何使用MindStudio进行Pytorch模型离线推理
摘要:本教程所示例的任务是Ascend Pytorch离线推理,即对给定的已经训练好的模型参数和推理脚本,在Ascend 310/710和Tesla设备上进行推理应用. 本文分享自华为云社区<使 ...