我正在尝试创建一个工厂函数,该函数执行具有给定延迟的特定异步函数。
出于这个问题的目的,这将是我所指的异步函数:
/*
* This is a simulation of an async function. Be imaginative!
*/
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
此函数采用url
并返回包含该URL和一些数据的JSON对象。
在我的代码周围,我通过以下方式调用此函数:
asyncMock('http://www.bananas.pt')
.then(console.log);
asyncMock('http://www.berries.com')
.then(console.log);
//... badjillion more calls
asyncMock('http://www.oranges.es')
.then(console.log);
这里的问题是所有这些调用都是在完全相同的时间进行的,因此会重载asyncMoc
正在使用的资源。
为避免上一个问题,我希望延迟执行Xms对asyncMoc
的所有调用。
这是我假装的图片:
为实现这一目标,我写了以下方法:
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
let promise = Promise.resolve();
let delayAsync = function(url) {
return promise = promise.then(() => {
return new Promise(fulfil => {
setTimeout(() => {
console.log(`made request to ${url}`);
fulfil(asyncMock(url));
}, delayMs);
});
});
};
return Object.freeze({
delayAsync
});
};
/*
* 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 delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
这个工厂有一个名为delayAsync
的函数,可以将所有调用asyncMock
的时间延迟500毫秒。但是,它还会强制执行调用的嵌套等待前一个的结果 - 这不是预期的。
这里的目标是在每次500毫秒内拨打asyncMock
三次,在收到三次响应后拨打10秒,差异为500毫秒。
在这种方法中,我的目标是建立一个具有一系列参数的工厂。然后,每500ms,计时器将运行一个执行器,它将从该数组中获取一个参数并返回一个结果:
/*
* This is a simulation of an async function. Be imaginative!
*/
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
throttleMs
} = args;
let argsList = [];
let timer;
/*
* 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 delayAsync = function(url) {
argsList.push(url);
return new Promise(fulfil => {
if (timer === undefined) {
console.log('created timer');
timer = setInterval(() => {
if (argsList.length === 0) {
clearInterval(timer);
timer = undefined;
} else {
let arg = argsList.shift();
console.log('making request ' + url);
fulfil(asyncMock(arg));
}
}, throttleMs);
} else {
//what if the timer is already running? I need to somehow
//connect it to this call!
}
});
};
return Object.freeze({
delayAsync
});
};
/*
* 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 delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
// a ton of other calls in random places in code
这段代码更糟糕。它执行asyncMoch
3次没有任何延迟,总是使用相同的参数,然后因为我不知道如何完成我的else
分支,它什么都不做。
答案 0 :(得分:1)
好的,所以这是我解决问题的方法。抱歉,我必须重写您的代码才能更好地理解它。我希望你无论如何都能解释它并从中得到一些东西。
使用Promises(JSFiddle):
在彼此之间调用500毫秒function asyncFunc(url) {
return new Promise(resolve => {
setTimeout(function() {
resolve({ url: url, data: 'banana' });
}, 2000);
});
}
function delayFactory(delayMs) {
var delayMs = delayMs;
var queuedCalls = [];
var executing = false;
this.queueCall = function(url) {
var promise = new Promise(function(resolve) {
queuedCalls.push({ url: url, resolve: resolve });
executeCalls();
});
return promise;
}
var executeCalls = function() {
if(!executing) {
executing = true;
function execute(call) {
asyncFunc(call.url).then(function(result) {
call.resolve(result);
});
setTimeout(function() {
queuedCalls.splice(queuedCalls.indexOf(call), 1);
if(queuedCalls.length > 0) {
execute(queuedCalls[0]);
} else {
executing = false;
}
}, delayMs)
}
if(queuedCalls.length > 0) {
execute(queuedCalls[0]);
}
}
}
}
var factory = new delayFactory(500);
factory.queueCall('http://test1').then(console.log); //2 sec log {url: "http://test1", data: "banana"}
factory.queueCall('http://test2').then(console.log); //2.5 sec log {url: "http://test2", data: "banana"}
factory.queueCall('http://test3').then(console.log); //3 sec log {url: "http://test3", data: "banana"}
factory.queueCall('http://test4').then(console.log); //3.5 sec log {url: "http://test4", data: "banana"}
答案 1 :(得分:1)
我将假设您希望delayAsync
返回的承诺根据asyncMock
的承诺来解决。
如果是这样,我会使用基于承诺的方法并像这样修改它(见注释):
// 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);
});
};
这确保了对asyncMock
的每个调用在前一个调用之后至少为delayMs
(由于计时器变幻而给予或花费一毫秒),并确保第一个调用延迟至少{{ 1}}。
带有一些调试信息的实例:
delayMs
let lastActualCall = 0; // Debugging only
let asyncMock = function(url) {
// Start debugging
// Let's show how long since we were last called
console.log(Date.now(), "asyncMock called", lastActualCall == 0 ? "(none)" : Date.now() - lastActualCall);
lastActualCall = Date.now();
// End debugging
return new Promise(fulfil => {
setTimeout(() => {
console.log(Date.now(), "asyncMock fulfulling");
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
// Seed our "last call at" value
let lastCall = Date.now();
let delayAsync = function(url) {
// Our new promise
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;
console.log(Date.now(), "scheduling w/delay =", thisDelay);
setTimeout(() => {
// Fulfill our promise using the result of `asyncMock`'s promise
fulfil(asyncMock(url));
}, thisDelay);
});
};
return Object.freeze({
delayAsync
});
};
/*
* 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 delayer = delayFactory({
delayMs: 500
});
console.log('running');
delayer.delayAsync('http://www.bananas.pt')
.then(console.log)
.catch(console.error);
delayer.delayAsync('http://www.fruits.es')
.then(console.log)
.catch(console.error);
// Let's hold off for 100ms to ensure we get the spacing right
setTimeout(() => {
delayer.delayAsync('http://www.veggies.com')
.then(console.log)
.catch(console.error);
}, 100);
答案 2 :(得分:1)
在阅读了这两个解决方案之后,我不得不说我非常感谢那些花时间帮助我的人。这样的时刻(虽然很少见)让我为拥有StackOverflow帐户感到自豪。
这就是说,在阅读了这两个提案之后,我自带了一个,我将解释哪一个我觉得最好,为什么。
我的解决方案基于@ Arg0n的提议,它是使用JavaScript中的工厂模式简化/重新实现他的代码,并由Douglas Crockford使用ECMA6功能进行辩护:
let asyncFunc = function(url) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve({
url: url,
data: 'banana'
});
}, 5000);
});
};
let delayFactory = function(args) {
let {
delayMs
} = args;
let queuedCalls = [];
let executing = false;
let queueCall = function(url) {
return new Promise((resolve, reject) => {
queuedCalls.push({
url,
resolve,
reject
});
if (executing === false) {
executing = true;
nextCall();
}
});
};
let execute = function(call) {
console.log(`sending request ${call.url}`);
asyncFunc(call.url)
.then(call.resolve)
.catch(call.reject);
setTimeout(nextCall, delayMs);
};
let nextCall = function() {
if (queuedCalls.length > 0)
execute(queuedCalls.shift());
else
executing = false;
};
return Object.freeze({
queueCall
});
};
let myFactory = delayFactory({
delayMs: 1000
});
myFactory.queueCall('http://test1')
.then(console.log)
.catch(console.log);
myFactory.queueCall('http://test2')
.then(console.log)
.catch(console.log);
myFactory.queueCall('http://test3')
.then(console.log)
.catch(console.log);

为什么我要发布这个额外的解决方案?因为我认为这是对Arg0n提案的巨大改进,原因如下:
!executing
)是JavaScript中的一个问题。我强烈推荐JavaScript Appendix A: Awful parts。catch
失败asyncMock
Array.prototype.shift
代替Array.prototype.splice
,更容易阅读并提高效果。new
关键字,不要弄乱this
引用如果你喜欢Arg0n的解决方案,我建议你看看我的。
哪种解决方案更好,为什么?
起初我倾向于Arg0n的解决方案,因为它从我失败的尝试中获取灵感并使其成功。就其本身而言,这是非凡的。
此外,JavaScript中的Timers存在精度问题,并且在使用数字进行计算时JavaScript也存在问题(检查0.1 + 0.2!== 0.3)。
但是,两种解决方案都使用Timers。实际上,您需要计时器才能实现此行为。此外@ T.J。 Crowder的解决方案不是使用浮点算术,而是整数,因此他的计算是安全和合理的。
有人可能会指出Math
库是从java导入的JavaScript中的一个错误,但老实说这很远,而且没有任何问题。
此外,由于T.J。的解决方案没有像Arg0n的解决方案那样的数据结构,因此它的代码较小,因为它包含较少的逻辑来维护。从技术角度来看,毫无疑问,在这种具体情况下,他的解决方案是可行的。
然而,对于那些不熟悉数学的人来说,Arg0n的大道非常扎实。
从技术角度来看,T.J。的解决方案获胜。不过我可以说我很喜欢Arg0n的解决方案,特别是我的帖子版本,这是我可能会使用的版本。
我希望这篇文章可以帮助将来的某个人!