前两天有个同学在面试中被问到了一个问题:“如果在请求多个不同的接口,那么应该如何去做?” 该同学回答说:“我们可以把这些接口放到一个数组中,然后通过 for 循环来循环请求!”
嗯...这确是是一个方式,不过这并不好。再加上异步问题现在已经成了面试中的常见问题,所以,今天咱们就来说一下 异步请求的最佳实践,帮助大家解决异步编程,以及面试问题。
01:不使用 await 的循环请求
我们不应该在循环内使用 await。 而是可以利用 promise.all 方法:
// ❌
async function fn(reqs) {
const results = [];
for (const req of reqs) {
// 每次循环迭代都会延迟到整个异步操作完成
results.push(await req);
}
return results;
}
// ✅
async function fn(reqs) {
// 存储所有异步操作的 Promise
const promises = reqs.map((req) => req);
// 所有异步操作都已经开始,现在等待它们全部完成
const results = await Promise.all(promises);
return results
}
02:不要在 promise 中执行返回操作
不要在 Promise 构造函数中返回值。 从那里返回的值是无用的。 它们也不影响 Promise 的状态。
- 正确的方法是使用 resolve 传递值。
- 如果有错误,则使用 reject 传递错误。
// ❌
new Promise((resolve, reject) => {
// 返回没有意义
if (b) {
return result;
}
});
// ✅
new Promise((resolve, reject) => {
// 利用 resolve 传递
if (b) {
resolve(result);
return;
}
});
03:避免竞态问题
看以下代码,你认为最终打印会是多少?
// ❌
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 sleep (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, time)
})
}
async function addPosts(userId) {
// 将用户的帖子数量加到总帖子数上
totalPosts += await getPosts(userId);
}
// 并行地获取两个用户的帖子数量,并在全部获取完毕后输出总帖子数
await Promise.all([addPosts(1), addPosts(2)]);
console.log('帖子数量:', totalPosts);
执行以上代码,你可能会打印 3 或 5,而不是 8。
这个原因就是因为 竞态条件 问题而导致的。当在单独的函数调用中更新值时,更新不会反映在当前函数作用域中。 因此,这两个函数都将其结果添加到初始的 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 sleep (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, time)
})
}
async function addPosts(userId) {
const posts = await getPosts(userId);
totalPosts += posts; // 变量被读取并立即更新
}
await Promise.all([addPosts(1), addPosts(2)]);
console.log('帖子数量:', totalPosts);
04:避免回调地狱
这个问题大家应该很好理解,直接看代码即可
// ❌
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);
05:避免直接返回 await
尽量避免直接返回 await ,特别是在需要 try..catch 的时候:
// ❌
async () => {
try {
return await getUser(userId);
} catch (error) {
// 输出错误
}
}
// ✅
async () => {
try {
const user = await getUser(userId);
return user;
} catch (error) {
// 输出错误
}
}
06:reject 最好配合 Error 使用
// ❌
Promise.reject('这是一个错误');
// ✅
Promise.reject(new Error('这是一个错误'));