I am trying to create a factory function that executes a specific async function with a given delay.
For the purposes of this question, this will be the async function I refer to:
/*
* This is a simulation of an async function. Be imaginative!
*/
let asyncMock = function(url) {
return new Promise(fulfil => {
setTimeout(() => {
fulfil({
url,
data: "banana"
});
}, 10000);
});
};
This function takes an url and it returns a JSON object containing that URL and some data.
All around my code, I have this function called in the following way:
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);
The problem here is that all these calls are made at exactly the same time, thus overloading the resources that asyncMoc is using.
To avoid the previous problem, I wish to delay the execution of all calls to asyncMoc by Xms.
Here is a graphic with what I pretend:

To achieve this I wrote the following approaches:
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);
This factory has a function called delayAsync that will delay all calls to asyncMock by 500ms.However, it also forces the nest execution of the call to wait for the result of the previous one - which in not intended.
The objective here is to make three calls to asyncMock within 500ms each, and 10s after receive three responses with a difference of 500ms.
In this approach, my objective is to have a factory which has an array of parameters. Then, every 500ms, the timer will run an executor which will take a parameter from that array and return a result with it:
/*
* 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
This code is even worse. It executes asyncMoch 3 times without any delay whatsoever, always with the same parameter, and then because I don't know how to complete my else branch, it does nothing.
I'm going to assume you want the promises returned by delayAsync to resolve based on the promises from asyncMock.
If so, I would use the promise-based approach and modify it like this (see comments):
// 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);
});
};
That ensures that each call to asyncMock is at least delayMs after the previous one (give or take a millisecond thanks to timer vagaries), and ensures the first one is delayed by at least delayMs.
Live example with some debugging info:
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);
.as-console-wrapper {
max-height: 100% !important;
}
Okay, so here's my solution to your problem. Sorry I had to rewrite your code to better be able to understand it. I hope you can interpret it anyway and get something out of it.
Calls 500ms between eachother using Promises (JSFiddle):
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"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With