JavaScript 常见的规范异步代码的ESLint 规则

异步操作包括:

  • 事件循环
  • 回调函数
  • promise 和 async/await

ESLint 中文文档:https://www.tkcnn.com/eslint/rules/no-promise-executor-return.html

no-async-promise-executor

报错解释:

no-async-promise-executor 用于检测在创建 Promise 时,其执行器(executor)函数是否包含异步操作。

不应该将一个异步函数(async函数)直接传递给Promise的构造函数。因为JavaScript中的Promise构造函数接受的应该是一个同步的执行函数,它不应该执行异步操作。

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

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

JavaScript中的Promise是用来进行异步编程的一个重要机制。Promise对象代表一个即将完成、失败或进行中的操作。Promise构造函数接受一个执行函数作为参数,这个执行函数接受两个参数:resolve和reject。resolve函数在操作成功完成时调用,而reject函数在操作失败时调用。

异步函数使用async关键字定义,它自动返回一个Promise对象。当你在async函数中执行异步操作时,它会返回一个Promise对象,该对象在未来某个时刻将会被解决或拒绝。

问题在于,如果你将一个async函数直接传递给Promise构造函数,实际上你会得到一个立即解决的Promise,其值就是这个async函数的返回值,而这个返回值实际上是另一个Promise。这样会导致逻辑上的混乱,并可能不按预期工作。另外,如果 async 函数抛出了异常,新构造的 promise 实例并不会 reject ,那么这个错误就捕获不到了。

解决方法:

  1. 如果确实需要在 Promise 的执行器中进行异步操作,确保正确地处理 Promise 链,使用 async/await 来等待异步操作完成。
  2. 如果执行器不需要异步操作,移除任何返回 Promise 的调用,确保执行器函数是同步的
// 正确的使用Promise的方式:移除异步操作
const doSomethingAsync = () => {
  return new Promise((resolve, reject) => {
    // 在这里执行异步操作
    someAsyncOperation((error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
};

而不是将async函数直接传递给Promise构造函数:

// 这样做是错误的
const asyncFunction = async () => {
  // 异步操作
};
 
// 这里会创建一个立即解决的Promise
const wrongPromise = new Promise(asyncFunction);

如果你需要将一个现有的async函数转化为Promise,你可以直接调用这个async函数,它会返回一个Promise对象。例如:

const asyncFunction = async () => {
  // 异步操作
};
 
const promise = asyncFunction();

总结:避免将async函数传递给Promise构造函数,而是直接调用async函数来获取返回的Promise对象。

no-await-in-loop

不建议在循环里使用 await,有这种写法通常意味着程序没有充分利用 JavaScript 的事件驱动。

在迭代器的每个元素上执行运算是个常见的任务。然而,每次运算都执行 await,意味着该程序并没有充分利用 async/await 并行的好处。

通常,代码应该重构为立即创建所有 promise,然后通过 Promise.all() 访问结果。否则,每个后续的操作将不会执行,直到前一个操作执行完毕。

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

no-promise-executor-return

no-promise-executor-return表示禁止从 Promise 执行器函数返回值。根据这条规则,不建议在 Promise 构造函数中返回值,Promise 构造函数中返回的值是没法用的,并且返回值也不会影响到 Promise 的状态。相反,它们应该只使用执行器参数提供的resolve和reject函数。

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

正常的做法是将返回值传递给 resolve,如果出错了就传给 reject

// ✅
const myPromise = new Promise((resolve, reject) => {
  // Do some asynchronous operations
  if (/* operation successful */) {
    resolve(someValue);
  } else {
    reject(new Error('Operation failed'));
  }
});

遵循此规则可确保基于承诺的代码保持清晰,并遵循预期的异步流,而不依赖于执行器函数本身的返回值。

require-atomic-updates

require-atomic-updates是Node.js和JavaScript中的一个规则,它强调了依赖异步操作但不能正确处理它们的代码的潜在问题。具体来说,它解决了以可能导致竞争条件或意外行为的方式访问或修改变量的情况。
此规则有助于确保正确处理异步操作,特别是在处理共享资源或可变状态时。不建议将赋值操作和 await 组合使用,这可能会导致条件竞争。

“require-atomic-updates”的名称表明在处理异步操作时原子更新是必需的。原子更新是一种完全发生或根本不发生的操作,不会被其他操作中断。在JavaScript上下文中,这通常涉及使用适当的同步机制,如锁、互斥锁或原子操作,以确保安全地访问或修改共享资源。

下面是一个可能触发require-atomic-updates规则的代码示例:

let counter = 0;

async function incrementCounter() {
  counter++; // This operation is not atomic
}

incrementCounter();

在本例中,计数器++操作不是原子的,这意味着它不能保证完全不中断地执行,特别是在异步操作的上下文中。为了解决这个问题,可以使用锁或互斥锁等同步机制来确保操作是原子性的,并且可以安全地访问共享资源。

let counter = 0;
const mutex = new Mutex();

async function incrementCounter() {
  await mutex.lock();
  counter++;
  mutex.unlock();
}

incrementCounter();

max-nested-callbacks

许多JavaScript库使用回调模式来管理异步操作。任何复杂性的程序都很可能需要管理多个并发级别的异步操作。容易陷入的一个常见陷阱是嵌套回调,这使得代码更难以更深地读取回调嵌套。

此规则防止回调地狱,避免大量的深度嵌套:

/* 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);

回调地狱让代码难以阅读和维护,建议将回调都重构为 Promise 并使用现代的 async/await 语法。

no-return-await

禁止不必要的 return await。返回异步结果时不一定要写 await ,如果你要等待一个 Promise,然后又要立刻返回它,这可能是不必要的。通过避免不必要的承诺链来提高代码的可读性和性能。
在async函数中使用return await时,实际上是在等待promise解析后返回其结果。然而,由于异步函数已经返回承诺,可以直接返回承诺而不使用await。

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

从一个 async 函数返回的所有值都包含在一个 Promise 中,你可以直接返回这个 Promise

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

当然,也有个例外,如果外面有 try...catch 包裹,删除 await 就捕获不到异常了,在这种情况下,建议明确一下意图,把结果分配给不同行的变量。

// 👎
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

建议在 reject Promise 时强制使用 Error 对象。

  • 更好的错误跟踪:当使用 Error 对象时,你可以获得堆栈跟踪,这有助于确定错误发生的位置。而如果你只传递一个字符串,那么你将失去这个有用的信息。
  • 更一致的错误处理:使用 Error 对象作为拒绝的原因,可以确保你的代码在处理错误时具有一致性。你可以使用 instanceof Error 来检查错误对象,或者访问其 .message 和 .stack 属性。
  • 更好的错误消息:Error 对象允许你提供一个自定义的错误消息,这可以使错误更容易理解。
// ❌
Promise.reject('An error occurred');

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

node/handle-callback-err

强制在 Node.js 的异步回调里进行异常处理。

在 Node.js 中,许多 API 都是异步的,并使用回调函数来报告操作的结果。通常,这些回调函数接受两个参数:第一个参数是错误对象(如果有的话),第二个参数是操作的结果。

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

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

  console.log(data);
}

Node.js 中,通常将异常作为第一个参数传递给回调函数。忘记处理这些异常可能会导致你的应用程序出现不可预知的问题。

如果函数的第一个参数命名为 err 时才会触发这个规则,你也可以去 .eslintrc 文件里自定义异常参数名。

node/no-sync

不建议在存在异步替代方案的 Node.js 核心 API 中使用同步方法。

在 Node.js 中,同步方法会阻塞事件循环,直到操作完成。这意味着,如果一个同步操作需要很长时间才能完成(比如,读取一个大文件),那么整个应用程序在此期间将无法处理其他请求或事件。这可能导致应用程序响应缓慢或超时,尤其是在高并发环境下。

相比之下,异步方法允许 Node.js 在等待操作完成时继续处理其他任务。这使得应用程序能够更有效地利用系统资源,并提供更好的性能和响应性。

node/no-sync 规则会检查代码中是否使用了不被推荐的同步方法,并发出警告。这些同步方法包括但不限于:

  • fs.readFileSync()
  • dns.lookupSync()
  • 数据库连接的同步方法(如 MongoDB 的 db.collection.find().toArray())

要遵循这个规则,你应该尽量使用异步方法替代同步方法。例如,使用 fs.readFile() 替代 fs.readFileSync()。如果你确实需要同步操作,并且理解其潜在的性能影响,你可以通过禁用该规则或添加注释来避免警告。

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

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

Node.js 中对 I/O 操作使用同步方法会阻塞事件循环。大多数场景下,执行 I/O 操作时使用异步方法是更好的选择。

@typescript-eslint/await-thenable

属于 @typescript-eslint 插件的一部分。这个规则专门用于检查在 TypeScript 代码中使用 await 关键字时,被 await 的表达式是否返回了一个 Thenable(即具有 then 方法的对象)。

在 JavaScript 和 TypeScript 中,await 关键字用于等待一个 Promise 完成,并返回 Promise 的解析值。如果你尝试 await 一个非 Thenable 对象(即没有 then 方法的对象),TypeScript 编译器会抛出一个错误。

然而,有时开发者可能不小心或错误地 await 一个非 Thenable 对象。@typescript-eslint/await-thenable 规则就是为了捕获这种情况,确保代码的正确性。

// ❌
function getValue() {
  return someValue;
}

await getValue();

// 要修复这个错误,你需要确保 await 的表达式返回一个 Promise 或其他 Thenable 对象:
// ✅
async function getValue() {
  return someValue;
}

await getValue();

async function getValue() {
  const result = await Promise.resolve("a string wrapped in a Promise"); // 正确
  console.log(result);
}

@typescript-eslint/no-floating-promises

属于 @typescript-eslint 插件的一部分。这个规则专门用于检查 TypeScript 代码中是否存在“漂浮的 Promise”(floating promises)。建议 Promise 附加异常处理的代码。

一个“漂浮的 Promise”是指一个没有被正确处理(例如:使用 .catch() 捕获错误或使用 await 等待其完成)的 Promise。当这样的 Promise 出现错误时,错误可能会被静默忽略,而不会被应用程序捕获或记录,从而导致难以调试的问题。

在 TypeScript 或 JavaScript 中,当你调用一个返回 Promise 的函数时,通常你会想要处理这个 Promise,以确保它能够成功完成或捕获其可能抛出的任何错误。如果你忘记这样做,就会出现一个漂浮的 Promise。

为了遵循 @typescript-eslint/no-floating-promises 规则,你应该确保每个 Promise 要么被 await 等待,要么被 .then() 或 .catch() 处理:

// ❌
myPromise()
  .then(() => {});
  
async function doSomething() {
  someAsyncFunction(); // 忘记处理返回的 Promise
}

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

async function doSomething() {
  try {
    await someAsyncFunction(); // 使用 await 等待 Promise
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

@typecript-eslint/no-misused-promises

属于 @typescript-eslint 插件的一部分。这个规则用于检查 TypeScript 代码中 Promise 的错误使用,以防止潜在的逻辑错误和不可预见的行为。

该规则会检查以下几个方面:

  • Promise 链中的错误处理:如果 Promise 链中的某个 .then() 没有配对的 .catch() 来处理可能的错误,该规则会发出警告。这有助于确保错误不会被静默忽略,而是被适当地捕获和处理。
  • Promise 作为值返回:如果一个函数返回一个 Promise,但调用者没有正确处理这个 Promise(例如,没有使用 await 或 .then()/.catch()),该规则可能会发出警告。这有助于确保 Promise 的结果或错误得到适当的处理。
  • Promise 的不必要包装:如果一个值已经被包装在一个 Promise 中,但又被再次包装,该规则可能会警告这种不必要的双重包装。这通常是由于误解 Promise 的工作方式而导致的。
  • Promise 的静态方法使用:该规则还可能检查 Promise 的静态方法(如 Promise.all()、Promise.race() 等)的使用,以确保它们被正确使用。
// 错误示例:Promise 链中缺少错误处理
async function example1() {
  await someAsyncFunction()
    .then(result => {
      // 处理结果,但没有 .catch() 处理错误
    });
}

// 错误示例:不必要的 Promise 包装
function example2(value: any) {
  return new Promise(resolve => {
    resolve(value); // value 已经被包装在 Promise 中了
  });
}

// 错误示例:Promise 的错误使用
function example3(): Promise<void> {
  // 这个函数返回一个 Promise,但调用者可能没有正确处理它
}

// 正确的做法:确保 Promise 链有错误处理
async function correctUsage1() {
  try {
    const result = await someAsyncFunction();
    // 处理结果
  } catch (error) {
    // 处理错误
  }
}

// 正确的做法:避免不必要的 Promise 包装
function correctUsage2(value: any) {
  return value; // 如果 value 已经是 Promise,则不需要再次包装
}

// 正确的做法:确保调用者正确处理返回的 Promise
function correctUsage3(): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    // 一些异步操作
  });
}

// 调用者正确处理 Promise
correctUsage3()
  .then(() => {
    // 成功处理
  })
  .catch((error) => {
    // 错误处理
  });

不建议将 Promise 传递到并非想要处理它们的地方,例如 if 条件。

// ❌
if (getUserFromDB()) {}

// ✅ 👎
if (await getUserFromDB()) {}

更推荐抽一个变量出来提高代码的可读性。

// ✅ 👍
const user = await getUserFromDB();
if (user) {}
  • 35
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡夫卡的小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值