Skip to content

场景题-请求合并

面试中常考的一个代码题,也是业务开发中经常会遇到的问题

题目描述

短时间内需要请求多个同类型的资源

期望多个请求合并成一个请求发送

例如:

  • 有一个接口其请求路径为 /path
  • query有一个id参数支持传一个或者多个id
    • /path?id=1
    • /path?id=1,2,3
  • 对应的响应格式为
js
{
    code:0,
    data:{
        1:{},
    },
    errMsg:'success'
}
// or
{
    code:0,
    data:{
        1:{},
        2:{},
        3:{}
    },
    errMsg:'success'
}

request 方法示例

js
request({
    url:'/path',
    query:{
        id: '0'
    }
})

要求

实现一个 getArticle 方法,每个方法回调最终拿到的是自己需要的内容,且短时间内只发出了一次请求

js
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'
//     }
// })

题目分析

场景分析

常见需要短时间请求多个同类型资源的场景就是资源懒加载的时候

  • 如:一个文章列表的中,获取每个展示文章点赞/评论数是单独的一个接口
  • 当一次性需要展示多条新闻,那么就要发起多个查询点赞/评论的请求

后端为了减少处理请求的压力,通常会让一个接口支持同时查询多条数据的能力

考点

  1. 怎么确定这个短时间是多久?
    • 其实这里就是考察到了event loop,这个短时间就是指同一个周期内,然后就变成了合并同一个周期内的请求,如何保证在一个周期内,这里就可以用到防抖,让请求在执行宏任务的时候发出
  2. 如何让每个方法拿到自己需要的数据?
    • 这里可以在方法内部用一个map将每个方法的Promise的resolve存起来,每个方法传参的id作为key,在接口响应后,将对应数据通过key取出,然后从map中取出对应Promise的resolve然后执行resolve(data[id])即可

需要考虑的问题

  1. 如果多个请求参数是一样的那么,最终请求的参数只有一个
    • 如 连续调用两次 getArticle(3)
    • 那么 请求的query依旧是 {id:'3'}而不是 {id:'3,3'}
    • 并且这两个请求的方法都需要得到响应
  2. 如果这个请求没有被按时响应,不能影响下一次发送

代码实现

js
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

js
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)
    })
}

测试用例

js
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)

打印结果

图片

总结

  1. 考察知识点 event loop,防抖,Promise
  2. 这是一个很常见的业务问题,考察面试者的动手实践能力

代码仅供参考,如有考虑不周之处,还请斧正