今天给大家继续分享一篇经典的前端面试题,来自阿里核心部门P7的一次面试。
假设现在后端有一个服务,支持批量返回书籍信息,它接受一个数组作为请求数据,数组储存了需要获取书目信息的书目 id,这个服务 fetchBooksInfo 大概是这个样子:
const fetchBooksInfo = bookIdList => {
// ...
return ([{
id: 123,
// ...
},
{
id: 456
// ...
},
// ...
])
}
fetchBooksInfo 已经给出,但是这个接口最多只支持 100 个 id 的查询。
现在需要开发者实现 getBooksInfo 方法,该方法:
getBooksInfo(123).then(data => {console.log(data.id)}) // 123
getBooksInfo(123).then(data => {console.log(data.id)}) // 123
getBooksInfo(456).then(data => {console.log(data.id)}) // 456
*注意这里必须只发送一个请求,也就是说调用了一次 fetchBooksInfo。
要考虑服务端出错的情况,比如批量接口请求 [123, 446] 书目信息,但是服务端只返回了书目 123 的信息。此时应该进行合理的错误处理。
对 id 重复进行处理
题目分析
100 毫秒内的连续请求,要求进行合并,只触发一次网络请求。因此需要一个 bookIdListToFetch 数组,并设置 100 毫秒的定时。在 100 毫秒以内,将所有的书目 id push 到 bookIdListToFetch 中,bookIdListToFetch 长度为 100 时,进行 clearTimeout,并调用 fetchBooksInfo 发送请求
因为服务端可能出错,返回的批量接口结果可能缺少某个书目信息。我们需要对相关的调用进行抛错,比如 100 毫秒内连续调用:
getBooksInfo(123).then(data => {console.log(data.id)}) // 123
getBooksInfo(456).then(data => {console.log(data.id)}) // 456
我们要归并只调用一次 fetchBooksInfo:
fetchBooksInfo(123, 456)
如果返回有问题,只返回了:
[{
id: 123
//...
}]
没有返回 id 为 456 的书信息,需要:
getBooksInfo(456).then(data => {console.log(data.id)}).catch(error => {
console.log(error)
})
捕获错误。
这样一来,我们要对每一个 getBooksInfo 对应的 promise 实例的 reject 和 resolve 方法进行存储,存储在内存 promiseMap 中,以便在合适的时机进行 reject 或 resolve 对应的 promise 实例。
请看代码(对边界 case 的处理省略),我加入了关键注释:
// 储存将要请求的 id 数组
let bookIdListToFetch = []
// 储存每个 id 请求 promise 实例的 resolve 和 reject
// key 为 bookId,value 为 resolve 和 reject 方法,如:
// { 123: [{resolve, reject}]}
// 这里之所以使用数组存储 {resolve, reject},是因为可能存在重复请求同一个 bookId 的情况。其实这里我们进行了滤重,没有必要用数组。在需要支持重复的场景下,记得要用数组存储
let promiseMap = {}
// 用于数组去重
const getUniqueArray = array => Array.from(new Set(array))
// 定时器 id
let timer
const getBooksInfo = bookId => new promise((resolve, reject) => {
promiseMap[bookId] = promiseMap[bookId] || []
promiseMap[bookId].push({
resolve,
reject
})
const clearTask = () => {
// 清空任务和存储
bookIdListToFetch = []
promiseMap = {}
}
if (bookIdListToFetch.length === 0) {
bookIdListToFetch.push(bookId)
timer = setTimeout(() => {
handleFetch(bookIdListToFetch, promiseMap)
clearTask()
}, 100)
}
else {
bookIdListToFetch.push(bookId)
bookIdListToFetch = getUniqueArray(bookIdListToFetch)
if (bookIdListToFetch.length >= 100) {
clearTimeout(timer)
handleFetch(bookIdListToFetch, promiseMap)
clearTask()
}
}
})
const handleFetch = (list, map) => {
fetchBooksInfo(list).then(resultArray => {
const resultIdArray = resultArray.map(item => item.id)
// 处理存在的 bookId
resultArray.forEach(data => promiseMap[data.id].forEach(item => {
item.resolve(data)
}))
// 处理失败没拿到的 bookId
let rejectIdArray = []
bookIdListToFetch.forEach(id => {
// 返回的数组中,不含有某项 bookId,表示请求失败
if (!resultIdArray.includes(id)) {
rejectIdArray.push(id)
}
})
// 对请求失败的数组进行 reject
rejectIdArray.forEach(id => promiseMap[id].forEach(item => {
item.reject()
}))
}, error => {
console.log(error)
})
}
做出这道题的关键是: