题目描述
短时间内需要请求多个同类型的资源
期望多个请求合并成一个请求发送
例如:
- 有一个接口其请求路径为 /path
- query有一个id参数支持传一个或者多个id
/path?id=1
/path?id=1,2,3
- 对应的响应格式为
{ code:0, data:{ 1:{}, }, errMsg:'success' } // or { code:0, data:{ 1:{}, 2:{}, 3:{} }, errMsg:'success' }
request
方法示例
request({ url:'/path', query:{ id: '0' } })
要求
实现一个 getArticle
方法,每个方法回调最终拿到的是自己需要的内容,且短时间内只发出了一次请求
getArticle(3).then(res=>{}) getArticle(4).then(res=>{}) getArticle(5).then(res=>{}) getArticle(6).then(res=>{}) // request({ // url:'/path', // query:{ // id:'3,4,5,6' // } // })
题目分析
场景分析
常见需要短时间请求多个同类型资源的场景就是资源懒加载的时候
- 如:一个文章列表的中,获取每个展示文章点赞/评论数是单独的一个接口
- 当一次性需要展示多条新闻,那么就要发起多个查询点赞/评论的请求
后端为了减少处理请求的压力,通常会让一个接口支持同时查询多条数据的能力
考点
- 怎么确定这个短时间是多久?
- 其实这里就是考察到了event loop,这个短时间就是指同一个周期内,然后就变成了合并同一个周期内的请求,如何保证在一个周期内,这里就可以用到防抖,让请求在执行宏任务的时候发出
- 如何让每个方法拿到自己需要的数据?
- 这里可以在方法内部用一个map将每个方法的Promise的resolve存起来,每个方法传参的id作为key,在接口响应后,将对应数据通过key取出,然后从map中取出对应Promise的resolve然后执行
resolve(data[id])
即可
需要考虑的问题
- 如果多个请求参数是一样的那么,最终请求的参数只有一个
- 如 连续调用两次
getArticle(3)
- 那么 请求的query依旧是
{id:'3'}
而不是{id:'3,3'}
- 并且这两个请求的方法都需要得到响应
- 如果这个请求没有被按时响应,不能影响下一次发送
代码实现
var getArticle = (function () { let timer = null; let resolveMap = new Map(); return function (id) { return new Promise((resolve) => { // 这里用string类型作为key const key = `${id}`; const resolves = resolveMap.get(key); // 不存在则创建,因为可能有重复的id,所以这里value为数组 if (!resolves) { resolveMap.set(key, [resolve]); } else { // 存在则加入,因为是对象,map里存的引用,所以这里不需要重新执行set resolves.push(resolve) } if (timer) { clearTimeout(timer) } timer = setTimeout(() => { // 这里将把请求发出去,需要重置状态 // 所以将现有的保存下来 const _resolvesMap = resolveMap const keys = [..._resolvesMap.keys()] request({ url: '/path', query: keys.join(',') }).then(res => { const { data } = res // 执行resolve for (const key of keys) { const resolves = _resolvesMap.get(key) const v = data[key] resolves.forEach(r => r(v)) } }) // 请求发出后就初始化,以便用于下次请求 timer = null; resolveMap = new Map(); }) }); }; })();
测试
模拟实现一个request
function request(options = {}) { console.log(new Date(), '发起一次请求', '-------参数为:', options.query) return new Promise(res => { const { query } = options if (!query) { res({ data: {} }) return } const ids = query.split(',') const testData = ids.reduce((pre, id) => { pre[id] = { id, rand: Math.random() } return pre }, {}) // 模拟响应延迟 setTimeout(() => { res({ code: 0, data: testData, errMsg: 'ok' }) }, 500) }) }
测试用例
getArticle(1).then(console.log) getArticle(3).then(console.log) getArticle(2).then(console.log) getArticle(2).then(console.log) getArticle(1).then(console.log) new Promise((res) => { getArticle(4).then(console.log) res() }) setTimeout(() => { getArticle(1).then(console.log) getArticle(3).then(console.log) getArticle(2).then(console.log) getArticle(2).then(console.log) getArticle(1).then(console.log) }, 400)
打印结果
网络异常,图片无法展示
|
总结
- 考察知识点 event loop,防抖,Promise
- 这是一个很常见的业务问题,考察面试者的动手实践能力
代码仅供参考,如有考虑不周之处,还请斧正