这两天遇到一个场景,需要把中文的单位名称传给接口,获取对应的 id。但数据是从 excel 中读取的,有批量的数据需要处理,同时又基本都是同一个单位的,但偶尔可能会有其他单位的名称(比如该公司有子公司或者二级公司之类的)。
对于批量数据中都是同单位的名称,没必要每次都去请求接口,可以把相同公司的名字合并到一起,只发起一次接口请求即可。
解决方案的关键在于:如何把相同数据的请求缓存起来,然后在接口完成后触发。
在 JavaScript 中,我们可以利用Promise
的特性:
只有 Promise 被
resolve()
的时候,才会执行then()
。
这样,我们就可以利用 Promise 的特性,把相同参数的请求先放到数组中缓存起来,然后在接口完成后再触发。
1. 一个简单的例子 #
我们看个简单的例子:
let firstResolve = () => {};
const secondPromise = new Promise((resolve) => {
firstResolve = resolve;
});
secondPromise.then(() => {
console.log("secondPromise fullfilled");
});
setTimeout(() => {
firstResolve();
}, 2000);
secondPromise 的then()
方法什么时候执行,取决于 firstResolve()
的调用时机。
2. 再一个简单的例子 #
上面的例子很基础,可能还没太了解到多个请求如何得到响应。这里我们暂时不考虑参数是否相同的问题。
/**
* 合并多个请求,apiCall表示请求的接口,需要返回一个Promise
*/
const mergeRequests = (apiCall) => {
const queue = []; // 缓存多个请求
return (...args) => {
if (!queue.length) {
// 还没有任何请求,那就发起请求
const promise = apiCall(...args);
// 待请求完毕后,执行所有创建的 Promise
promise.then((response) => {
queue.forEach((resolve) => resolve(response));
});
}
/**
* 已经有请求在执行了,那就先创建一个Promise(),
* 这个 Promise 的`then()`什么时候执行,取决于 resolve() 的调用时机
*/
return new Promise((resolve) => {
queue.push(resolve);
});
};
};
执行:
const apiCall = (data) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("假装发起了一个请求", Date.now());
resolve(`Response with ${JSON.stringify(data)}`);
}, 600);
});
};
const fn = mergeRequests(apiCall);
// 发起多个相同的请求
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
从调用结果中,我们可以看到,只有第一个请求发起了,后面的请求都返回了一个 Promise,当第一个请求完成后,会触发所有创建的 Promise 的then()
方法。
3. 完整版 #
我们在上面已经可以合并请求了,但为了方便,没有区分参数,这里我们把参数的要素也加进来。
/**
* 合并多个请求
* @param {Promise} apiCall 表示请求的接口
*/
const mergeRequests = (apiCall) => {
const requestQueue = new Map(); // 存储请求的队列,键是参数,值是 resolve 的数组
return (...args) => {
const key = JSON.stringify(args); // 使用参数作为键
let promiseResolvers = requestQueue.get(key);
if (!promiseResolvers) {
// 如果没有队列,创建一个新的请求
const promise = apiCall(...args);
promiseResolvers = [];
promise
.then((response) => {
promiseResolvers.forEach((resolve) => resolve(response));
// 该参数对应的请求全部完成,清除key
requestQueue.delete(key);
})
.catch(() => {
// 请求失败,清除key
requestQueue.delete(key);
});
requestQueue.set(key, promiseResolvers);
}
// 返回一个新的promise,它将在原始请求完成时解决
return new Promise((resolve) => {
promiseResolvers.push(resolve);
});
};
};
执行:
const apiCall = (data) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("假装发起了一个请求", data);
resolve(`Response with ${JSON.stringify(data)}`);
}, 700);
});
};
const fn = mergeRequests(apiCall);
// 发起多个相同的请求
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ url: "https://www.xiabingbao.com" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ url: "https://www.xiabingbao.com" }).then(console.log);
fn({ wechat: "前端小茶馆" }).then(console.log);
fn({ wechat: "前端小茶馆" }).then(console.log);
执行结果:
从执行结果上可以看出,不同参数发起了不同的请求,而相同参数的请求,则只发起了一次。