面试官:来吧!实操一下promise
背景
参加了szml面试,一共三道笔试题。第一道是画div写样式写逻辑,后续两道是promise的场景应用。后续面试时,面试官着重对笔试题进行了提问,并且对后两个promise的题目进行了一个升级。本文是对第二题的一个总结。
原题复现
以下为第二题的题目
import { isEqual } from 'lodash-es';
/**
* 第二题
*/
// 核心用户请求
let _requestTime = 0;
const requestUserInfo = () => {
// 这个方法的实现不能修改
return Promise.resolve().then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
}).then(() => {
_requestTime++;
return {
nick: 'nick',
age: '18',
};
});
});
};
const getUserInfo = () => {}
const fn = async () => {
try {
// 模拟请求
const result = await Promise.all([
getUserInfo(),
new Promise((resolve) =>
setTimeout(async () => {
resolve(await getUserInfo());
}, 300)
),
new Promise((resolve) =>
setTimeout(async () => {
resolve(await getUserInfo());
}, 2300)
),
]);
console.log(_requestTime, result);
if (
!isEqual(result, [
{
nick: 'nick',
age: '18',
},
{
nick: 'nick',
age: '18',
},
{
nick: 'nick',
age: '18',
},
])
) {
throw new Error('Wrong answer');
}
return _requestTime === 1;
} catch (err) {
console.warn('测试运行失败');
console.error(err);
return false;
}
};
要求
实现 getUserInfo
方法,getUserInfo
是个通用接口,在各个模块里面都有可能使用。
requestUserInfo
模拟的是请求服务端真正获取用户信息的方法,下面为需求的简要概述:
- 在一个页面有 A, B, C 3个功能模块,A, B, C 模块渲染执行顺序不可控
- 每个模块都会调用 getUserInfo 这个方法, 这个方法是可以直接调用 requestUserInfo 获取用户信息
- 调用三次就会发起三次网络请求
- 现在需要优化 getUserInfo 这个方法, 保证 getUserInfo 方法3次调用后, 最终只会发出一次网络请求。
实现
/** 是否有请求正在处理 */
let isInRequest = false;
/** 存放结果,后续如果还有请求并且结果有数据则直接返回数据 */
let result = {};
// 缓存后续的请求
let list = [];
const returnAction = (resolve) => {
list.push(resolve)
list.forEach((fn) => fn(result))
list = [];
};
const getUserInfo = () => {
// 目标是只执行一次,前一次接口调用成功,后续就不需要调用了
return new Promise((resolve) => {
if (!isInRequest && !result.nick) {
isInRequest = true;
requestUserInfo().then((res) => {
isInRequest = false;
if (res.nick) {
result = {...res};
// 成功之后
returnAction(resolve);
}
})
} else {
if (isInRequest) {
// 请求正在进行,将resolve塞入list
list.push(resolve);
} else {
// 请求已完成,直接返回结果
returnAction(resolve);
}
}
})
}
- 设定一个标识
isInRequest
,是否有请求正在执行,用于后续调用时,让后续调用进行等待。 - 第一次请求,此时isInRequest为false且result为空,将isInRequest标识设置为true,表明有接口正在被请求。然后请求接口
requestUserInfo
,等待处理完成时,将标识设置为false,并且将结果存入result,并将结果通过resolve方法返回。 - 在后续请求时,若是isInRequest为true,就将resolve存入list,等待前面的请求完成,在resolve结果
- 若是后续请求时,若是isInRequest为false且result有数据,则直接resolve结果
- 接第二步,在请求结束时,需要将list中可能存在的resolve给处理
- 实现完成,最先调用的接口会执行一次,后续调用只会将第一次的结果拿到返回。
升级
面试官:现在题目是接口一定成功返回,若是接口不一定调用成功,比如第一次不成功,或者前两次都不成功,第三次才成功,这时候该如何处理?
我:
改造一下requestUserInfo方法,使得请求可能成功或者失败
const requestUserInfo = () => {
// 这个方法的实现不能修改
return Promise.resolve().then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟请求可能失败的情况
const random = Math.floor(Math.random() * 10) % 2 === 0;
if (random) {
console.log('调用成功')
resolve();
} else {
console.log('调用失败')
reject();
}
}, 1000);
}).then(() => {
_requestTime++;
return {
nick: 'nick',
age: '18',
};
}, () => {
return {
result: false,
};
});
});
};
理清一下思路
- 实现方式不变,若是第一次请求失败,则执行第二次请求,所以得设置另一个变量,用于存储在请求执行期间的其它请求
- 失败后的请求的resolve也需要存起来,等到后续请求调用成功时,执行返回成功的结果
// 缓存后续的请求
let action = [];
const handleGetInfo = (resolve) => {
requestUserInfo().then((res) => {
isInRequest = false;
if (res.nick) {
result = {...res};
// 成功之后
returnAction(resolve)
} else {
// 接口请求失败,将resove塞进list
list.push(resolve);
// 若是队列中有待执行的方法,取出一个进行执行
if (action.length) {
const fn = action.shift();
fn();
}
}
})
}
const getUserInfo = () => {
// 目标是只执行一次,前一次接口调用成功,后续就不需要调用了
return new Promise((resolve) => {
if (!isInRequest && !result.nick) {
isInRequest = true;
handleGetInfo(resolve);
} else {
if (isInRequest) {
// 说明有请求正在进行
// 这时候需要等待,存入栈中
action.push(() => handleGetInfo(resolve))
} else {
returnAction(resolve)
}
}
})
}
到这也算是成功的完成了要求。
结语
本篇总结了一下第二题。其实本人对于异步处理Promise一直都不是那么熟练,这几天也是着重看了一下红宝书和其它资料,收获良多。后续第三题是一个请求的并发操作,这几天我也会把它整理出来和大家分享探讨。若是大家有其它的处理方式或者是想沟通交流的内容,欢迎大家留言。
转载自:https://juejin.cn/post/7239925883405877305