大家好!当你处理不会立即发生的任务时,比如等待文件下载或消息发送时,使用 JavaScript 编写代码可能会很棘手。这就是异步代码。
今天,我将分享 11 个可以帮助你更好地编写这种代码的技巧。而且很酷的是,ESLint 可以帮助确保你遵循这些技巧。你可以选择哪些规则让 ESLint 进行检查。让我们开始,让你的 JavaScript 代码变得更好!
https://eslint.org/docs/latest/rules/no-async-promise-executor#rule-details
不建议将 async
函数传递给 new Promise
构造函数。
// ❌
const foo = new Promise(async (resolve, reject) => {
readFile('foo.txt', function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
// ✅
const foo = new Promise((resolve, reject) => {
readFile('foo.txt', function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
首先,如果在 Promise 的构造函数中使用 async
,可能不需要包装一个 Promise。此外,如果 async
函数抛出错误,新创建的 Promise 不会被拒绝。这意味着你无法捕获该错误。
https://eslint.org/docs/latest/rules/no-await-in-loop#rule-details
不应在循环中使用 await
。这通常意味着程序没有充分利用 JavaScript 的事件驱动特性。
最好同时运行这些异步任务。这样可以使代码运行得更快。
// ❌
async function foo(things) {
const results = [];
for (const thing of things) {
// 不好:每次循环迭代都会延迟,直到整个异步操作完成
results.push(await bar(thing));
}
return baz(results);
}
// ✅
async function foo(things) {
const results = [];
for (const thing of things) {
// 好:所有异步操作立即启动。
results.push(bar(thing));
}
// 现在,所有异步操作都在运行,我们在这里等待它们全部完成。
return baz(await Promise.all(results));
}
https://eslint.org/docs/latest/rules/no-promise-executor-return#rule-details
不要在 Promise 构造函数中返回值。从那里返回的值是无用的。它们也不会影响 Promise 的状态。正确的方法是将值传递给 resolve
。如果有错误,将其传递给 reject
。
// ❌
new Promise((resolve, reject) => {
if (someCondition) {
return defaultResult;
}
getSomething((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
// ✅
new Promise((resolve, reject) => {
if (someCondition) {
resolve(defaultResult);
return;
}
getSomething((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
https://eslint.org/docs/latest/rules/require-atomic-updates#rule-details
不要将赋值与 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);
totalPosts
可能会打印 3 或 5,而不是 8。你可以在浏览器中尝试一下。
问题在于读取 totalPosts
和更新它之间的间隙。这导致了竞态条件。当值在单独的函数调用中更新时,更新不会反映在当前函数范围内。因此,两个函数都将其结果添加到初始值为 0 的 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) {
const posts = await getPosts(userId);
totalPosts += posts; // 变量被读取并立即更新
}
await Promise.all([addPosts(1), addPosts(2)]);
console.log('Post count:', totalPosts);
https://eslint.org/docs/latest/rules/max-nested-callbacks#rule-details
为了避免回调地狱和深层嵌套:
// ❌
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
语法。
https://eslint.org/docs/latest/rules/prefer-promise-reject-errors#rule-details
在拒绝 Promise 时,建议始终使用 Error
对象。这样可以更容易地跟踪错误堆栈。
// ❌
Promise.reject('An error occurred');
// ✅
Promise.reject(new Error('An error occurred'));
始终在 Node.js 异步回调中处理异常。
// ❌
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.js 核心 API 中不建议使用同步方法,而有异步的替代方法时。
// ❌
const file = fs.readFileSync(path);
// ✅
const file = await fs.readFile(path);
在 Node.js 中使用同步方法进行 I/O 操作会阻塞事件循环。在大多数情况下,使用异步方法进行 I/O 是更好的选择。
不建议 await
非 Promise 函数或值。
// ❌
function getValue() {
return someValue;
}
await getValue();
// ✅
async function getValue() {
return someValue;
}
await getValue();
建议将异常处理代码附加到 Promises 上。
// ❌
myPromise()
.then(() => {});
// ✅
myPromise()
.then(() => {})
.catch(() => {});
养成一个好习惯,始终准备好处理异常!
不建议将 Promises 传递给不处理它们的地方,比如 if 条件。
// ❌
if (getUserFromDB()) {}
// ✅ 👎
if (await getUserFromDB()) {}
最好提取一个变量以提高代码的可读性。
// ✅ 👍
const user = await getUserFromDB();
if (user) {}
记住,编写干净高效的代码很重要。这不仅仅是让它工作,还要让它工作得好,并且易于他人理解。继续练习这些良好的习惯,愉快地编码吧!
感谢您成为 In Plain English 社区的一员!在您离开之前:
-
一定要为作者鼓掌和关注️👏 ️️
-
关注我们:X | LinkedIn | YouTube | Discord | Newsletter
-
访问我们的其他平台:Stackademic | CoFeed | Venture | Cubed
-
更多内容请访问 PlainEnglish.io