延迟对服务器的批量 GET 请求,JavaScript
Delay batch GET requests to server, JavaScript
背景
我正在向服务器发出一批 HTTP GET 请求,我需要限制它们以避免杀死糟糕的服务器。出于我的演示目的,这将是 GET 方法:
/*
* This function simulates a real HTTP GET request, that always takes 1 seconds
* to give a response. In this case, always gives the same response.
*/
let mockGet = function(url) {
return new Promise(fulfil => {
setTimeout(
url => {
fulfil({ url, data: "banana"});
},
1000, url);
});
};
我在几个地方和不同的上下文中使用 mockGet
,所以现在我想改用 getUrl
函数,该函数使用 mockGet
但将其节流到合理的水平步伐。
我的意思是我需要一个函数,当被多次调用时,它总是按给定的顺序执行,但在不同的执行之间有给定的延迟。
研究
我的第一个尝试是使用像 underscorejs
and lodash
这样的库来实现这个:
但它不够好,因为它们没有提供我想要的功能。
事实证明,我需要将每个调用保存在一个列表中,以便以后方便时调用它。按照这个逻辑,我找到了另一个答案:
- Call function multiple times in the same moment but execute different calls with delay in nodejs
这有点回答了我的问题,但有几个问题我打算解决。它有全局变量,没有关注点分离,迫使用户了解内部机制……我想要更简洁的东西,类似于 underscorejs 或 lodash 的东西,将所有这些隐藏在一个易于使用的函数后面。
代码
我对这个挑战的看法是使用带有 Promises 的工厂模式,以便 return 我需要的东西:
let getFactory = function(args) {
let {
throttleMs
} = args;
let argsList = [];
let processTask;
/*
* Every time this function is called, I add the url argument to a list of
* arguments. Then when the time comes, I take out the oldest argument and
* I run the mockGet function with it, effectively making a queue.
*/
let getUrl = function(url) {
argsList.push(url);
return new Promise(fulfil => {
if (processTask === undefined) {
processTask = setInterval(() => {
if (argsList.length === 0) {
clearInterval(processTask);
processTask = undefined;
}
else {
let arg = argsList.shift();
fulfil(mockGet(arg));
}
}, throttleMs);
}
});
};
return Object.freeze({
getUrl
});
};
这是我遵循的算法:
- 每次调用
getUrl
时,我都会将参数保存在一个列表中。
然后,我检查 setInterval
计时器是否已启动。
- 如果没有,那么我从给定的延迟开始,否则我什么也不做。
执行时,setInterval
函数检查参数队列。
- 如果它是空的,我会停止
setInterval
计时器。
- 如果它有参数,我从列表中删除第一个,然后用真正的
mockGet
函数及其结果实现 Promise。
问题
虽然所有的逻辑似乎都已到位,但这还行不通……还没有。当我使用它时会发生什么:
/*
* All calls to any of its functions will have a separation of X ms, and will
* all be executed in the order they were called.
*/
let throttleFuns = getFactory({
throttleMs: 5000
});
throttleFuns.getUrl('http://www.bananas.pt')
.then(console.log);
throttleFuns.getUrl('http://www.fruits.es')
.then(console.log);
throttleFuns.getUrl('http://www.veggies.com')
.then(console.log);
// a ton of other calls in random places in code
只打印第一个响应,其他不打印。
问题
- 我做错了什么?
- 我该如何改进这段代码?
您的代码存在问题,您只调用了 getUrl
中第一个承诺的 fulfil
方法,但调用了很多次。当第一次调用 fulfil
时,承诺将被解决,后续调用将被忽略。
使用链式承诺队列是安排调用的一种简单方法:
function delay(ms, val) {
return new Promise(resolve => {
setTimeout(() => {
resolve(val);
}, ms);
});
}
class Scheduler {
constructor(interval) {
this.queue = Promise.resolve();
this.interval = interval;
}
submit(fn) {
const result = this.queue.then(fn);
this.queue = this.queue.then(() => delay(this.interval));
return result;
}
wrapFn(fn) {
const _this = this;
return function() {
const targetThis = this, targetArgs = arguments;
console.log("Submitted " + arguments[0]); // for demonstration
return _this.submit(() => fn.apply(targetThis, targetArgs));
}
}
}
function mockGet(url) {
console.log("Getting " + url);
return delay(100, "Resolved " + url);
}
const scheduler = new Scheduler(500);
const getUrl = scheduler.wrapFn(mockGet);
getUrl("A").then(x => console.log(x));
getUrl("B").then(x => console.log(x));
getUrl("C").then(x => console.log(x));
setTimeout(() => {
getUrl("D").then(x => console.log(x));
getUrl("E").then(x => console.log(x));
getUrl("F").then(x => console.log(x));
}, 3000);
编辑:上面的代码片段被编辑为等待恒定的时间量而不是最后一个 mockGet 解析。虽然我不太明白你在这里的目标。如果您只有一个客户端,那么序列化 api 调用应该没问题。如果您有很多客户端,那么此解决方案将无济于事,因为在没有此限制的情况下,在一个时间段内将进行相同数量的调用,只是服务器上的负载将分散而不是突发。
编辑:使面向对象的代码更易于模块化以满足您的一些简洁代码要求。
解决方案
延迟批处理请求与延迟任何异步调用完全相同。经过多次尝试和询问,我终于找到了我在这个问题中寻找的解决方案:
有对推理的深入解释以及为什么我也选择了答案。
队列与数学
在上面的回答中,我比较了这个问题的解决方案。一种使用队列逻辑,就像我在这里尝试做的那样,另一种使用 Math.
通常,如果您有一个数学表达式,您最终需要的逻辑会更少,代码也更容易维护。这是我选择的解决方案。
// Seed our "last call at" value
let lastCall = Date.now();
let delayAsync = function(url) {
return new Promise(fulfil => {
// Delay by at least `delayMs`, but more if necessary from the last call
const now = Date.now();
const thisDelay = Math.max(delayMs, lastCall - now + 1 + delayMs);
lastCall = now + thisDelay;
setTimeout(() => {
// Fulfill our promise using the result of `asyncMock`'s promise
fulfil(asyncMock(url));
}, thisDelay);
});
};
但是,由于我在使用队列逻辑的道路上,我决定 post 我的 2 美分,我为此感到非常自豪。
要了解更多信息,我强烈建议您阅读整篇文章!
背景
我正在向服务器发出一批 HTTP GET 请求,我需要限制它们以避免杀死糟糕的服务器。出于我的演示目的,这将是 GET 方法:
/*
* This function simulates a real HTTP GET request, that always takes 1 seconds
* to give a response. In this case, always gives the same response.
*/
let mockGet = function(url) {
return new Promise(fulfil => {
setTimeout(
url => {
fulfil({ url, data: "banana"});
},
1000, url);
});
};
我在几个地方和不同的上下文中使用 mockGet
,所以现在我想改用 getUrl
函数,该函数使用 mockGet
但将其节流到合理的水平步伐。
我的意思是我需要一个函数,当被多次调用时,它总是按给定的顺序执行,但在不同的执行之间有给定的延迟。
研究
我的第一个尝试是使用像 underscorejs
and lodash
这样的库来实现这个:
但它不够好,因为它们没有提供我想要的功能。
事实证明,我需要将每个调用保存在一个列表中,以便以后方便时调用它。按照这个逻辑,我找到了另一个答案:
- Call function multiple times in the same moment but execute different calls with delay in nodejs
这有点回答了我的问题,但有几个问题我打算解决。它有全局变量,没有关注点分离,迫使用户了解内部机制……我想要更简洁的东西,类似于 underscorejs 或 lodash 的东西,将所有这些隐藏在一个易于使用的函数后面。
代码
我对这个挑战的看法是使用带有 Promises 的工厂模式,以便 return 我需要的东西:
let getFactory = function(args) {
let {
throttleMs
} = args;
let argsList = [];
let processTask;
/*
* Every time this function is called, I add the url argument to a list of
* arguments. Then when the time comes, I take out the oldest argument and
* I run the mockGet function with it, effectively making a queue.
*/
let getUrl = function(url) {
argsList.push(url);
return new Promise(fulfil => {
if (processTask === undefined) {
processTask = setInterval(() => {
if (argsList.length === 0) {
clearInterval(processTask);
processTask = undefined;
}
else {
let arg = argsList.shift();
fulfil(mockGet(arg));
}
}, throttleMs);
}
});
};
return Object.freeze({
getUrl
});
};
这是我遵循的算法:
- 每次调用
getUrl
时,我都会将参数保存在一个列表中。 然后,我检查
setInterval
计时器是否已启动。- 如果没有,那么我从给定的延迟开始,否则我什么也不做。
执行时,
setInterval
函数检查参数队列。- 如果它是空的,我会停止
setInterval
计时器。 - 如果它有参数,我从列表中删除第一个,然后用真正的
mockGet
函数及其结果实现 Promise。
- 如果它是空的,我会停止
问题
虽然所有的逻辑似乎都已到位,但这还行不通……还没有。当我使用它时会发生什么:
/*
* All calls to any of its functions will have a separation of X ms, and will
* all be executed in the order they were called.
*/
let throttleFuns = getFactory({
throttleMs: 5000
});
throttleFuns.getUrl('http://www.bananas.pt')
.then(console.log);
throttleFuns.getUrl('http://www.fruits.es')
.then(console.log);
throttleFuns.getUrl('http://www.veggies.com')
.then(console.log);
// a ton of other calls in random places in code
只打印第一个响应,其他不打印。
问题
- 我做错了什么?
- 我该如何改进这段代码?
您的代码存在问题,您只调用了 getUrl
中第一个承诺的 fulfil
方法,但调用了很多次。当第一次调用 fulfil
时,承诺将被解决,后续调用将被忽略。
使用链式承诺队列是安排调用的一种简单方法:
function delay(ms, val) {
return new Promise(resolve => {
setTimeout(() => {
resolve(val);
}, ms);
});
}
class Scheduler {
constructor(interval) {
this.queue = Promise.resolve();
this.interval = interval;
}
submit(fn) {
const result = this.queue.then(fn);
this.queue = this.queue.then(() => delay(this.interval));
return result;
}
wrapFn(fn) {
const _this = this;
return function() {
const targetThis = this, targetArgs = arguments;
console.log("Submitted " + arguments[0]); // for demonstration
return _this.submit(() => fn.apply(targetThis, targetArgs));
}
}
}
function mockGet(url) {
console.log("Getting " + url);
return delay(100, "Resolved " + url);
}
const scheduler = new Scheduler(500);
const getUrl = scheduler.wrapFn(mockGet);
getUrl("A").then(x => console.log(x));
getUrl("B").then(x => console.log(x));
getUrl("C").then(x => console.log(x));
setTimeout(() => {
getUrl("D").then(x => console.log(x));
getUrl("E").then(x => console.log(x));
getUrl("F").then(x => console.log(x));
}, 3000);
编辑:上面的代码片段被编辑为等待恒定的时间量而不是最后一个 mockGet 解析。虽然我不太明白你在这里的目标。如果您只有一个客户端,那么序列化 api 调用应该没问题。如果您有很多客户端,那么此解决方案将无济于事,因为在没有此限制的情况下,在一个时间段内将进行相同数量的调用,只是服务器上的负载将分散而不是突发。
编辑:使面向对象的代码更易于模块化以满足您的一些简洁代码要求。
解决方案
延迟批处理请求与延迟任何异步调用完全相同。经过多次尝试和询问,我终于找到了我在这个问题中寻找的解决方案:
有对推理的深入解释以及为什么我也选择了答案。
队列与数学
在上面的回答中,我比较了这个问题的解决方案。一种使用队列逻辑,就像我在这里尝试做的那样,另一种使用 Math.
通常,如果您有一个数学表达式,您最终需要的逻辑会更少,代码也更容易维护。这是我选择的解决方案。
// Seed our "last call at" value
let lastCall = Date.now();
let delayAsync = function(url) {
return new Promise(fulfil => {
// Delay by at least `delayMs`, but more if necessary from the last call
const now = Date.now();
const thisDelay = Math.max(delayMs, lastCall - now + 1 + delayMs);
lastCall = now + thisDelay;
setTimeout(() => {
// Fulfill our promise using the result of `asyncMock`'s promise
fulfil(asyncMock(url));
}, thisDelay);
});
};
但是,由于我在使用队列逻辑的道路上,我决定 post 我的 2 美分,我为此感到非常自豪。
要了解更多信息,我强烈建议您阅读整篇文章!