I understand how to use setTimeout function, but I can't find a way to create a function like it.
I have an example:
setTimeout(() => {
console.log('3s');
}, 3000);
while(1);
The result is setTimeout callback never call so I think it use the same thread like every js other functions. But when it's check the time reach or not? and how it can do that?
Updated
To avoid misunderstanding I update my question.
I can't find a way to create a async function with callback after specify time (without using setTimeout and don't block entire thread). This function setTimeout seen like a miracle to me. I want to understand how it work.
Just for the game since I really don't see why you couldn't use setTimeout...
To create a non-blocking timer, without using the setTimeout/setInterval methods, you have only two ways:
event based timer
run your infinite loop in a second thread
Event based timer
One naive implementation would be to use the MessageEvent interface and polling until the time has been reached. But that's not really advice-able for long timeouts as this would force the event-loop to constantly poll new tasks, which is bad for trees.
function myTimer(cb, ms) {
const begin = performance.now();
const channel = myTimer.channel ??= new MessageChannel();
const controller = new AbortController();
channel.port1.addEventListener("message", (evt) => {
if(performance.now() - begin >= ms) {
controller.abort();
cb();
}
else if(evt.data === begin) channel.port2.postMessage(begin);
}, { signal: controller.signal });
channel.port1.start();
channel.port2.postMessage(begin);
}
myTimer(() => console.log("world"), 2000);
myTimer(() => console.log("hello"), 100);
So instead, if available, one might want to use the Web Audio API and the AudioScheduledSourceNode, which makes great use of the high precision Audio Context's own clock:
function myTimer(cb, ms) {
if(!myTimer.ctx) myTimer.ctx = new (window.AudioContext || window.webkitAudioContext)();
var ctx = myTimer.ctx;
var silence = ctx.createGain();
silence.gain.value = 0;
var note = ctx.createOscillator();
note.connect(silence);
silence.connect(ctx.destination);
note.onended = function() { cb() };
note.start(0);
note.stop(ctx.currentTime + (ms / 1000));
}
myTimer(()=>console.log('world'), 2000);
myTimer(()=>console.log('hello'), 200);
Infinite loop on a different thread
Yes, using Web Workers we can run infinite loops without killing our web page:
function myTimer(cb, ms) {
var workerBlob = new Blob([mytimerworkerscript.textContent], {type: 'application/javascript'});
var url = URL.createObjectURL(workerBlob);
var worker = new Worker(url);
worker.onmessage = function() {
URL.revokeObjectURL(url);
worker.terminate();
cb();
};
worker.postMessage(ms);
}
myTimer(()=>console.log('world'), 2000);
myTimer(()=>console.log('hello'), 200);
<script id="mytimerworkerscript" type="application/worker-script">
self.onmessage = function(evt) {
var ms = evt.data;
var now = performance.now();
while(performance.now() - now < ms) {}
self.postMessage('done');
}
</script>
And for the ones who like to show off they know about the latest features not yet really available (totally not my style), a little mention of the incoming Prioritized Post Task API and its delayed tasks, which are basically a more powerful setTimeout, returning a promise, on which we can set prioritization.
(async () => {
if(globalThis.scheduler) {
const p1 = scheduler.postTask(()=>{ console.log("world"); }, { delay: 2000} );
const p2 = scheduler.postTask(()=>{ console.log("hello"); }, { delay: 1000} );
await p2;
console.log("future");
}
else {
console.log("Your browser doesn't support this API yet");
}
})();
The reason callback of setTimeout() is not being called is, you have while(1) in your code which acts as infinite loop. It will keep your javascript stack busy whole time and that is the reason event loop will never push callback function of setTimeout() in stack.
If you remove while(1) from your code, callback for setTimeout() should get invoked.
setTimeout(() => {
console.log('3s');
}, 3000);
To create your own setTimeout function, you can use the following function, setMyTimeout() to do that without using setTimeout.
var foo= ()=>{
console.log(3,"Called after 3 seconds",new Date().getTime());
}
var setMyTimeOut = (foo,timeOut)=>{
let timer;
let currentTime = new Date().getTime();
let blah=()=>{
if (new Date().getTime() >= currentTime + timeOut) {
clearInterval(timer);
foo()
}
}
timer= setInterval(blah, 100);
}
console.log(1,new Date().getTime());
setMyTimeOut(foo,3000)
console.log(2,new Date().getTime());
Following is the implementation of custom setTimeout and setInterval, clearTimeout and clearInterval. I created them to use in sandbox environments where builtin setTimeout and setInterval doesn't work.
const setTimeouts = [];
export function customSetTimeout(cb, interval) {
const now = window.performance.now();
const index = setTimeouts.length;
setTimeouts[index] = () => {
cb();
};
setTimeouts[index].active = true;
const handleMessage = (evt) => {
if (evt.data === index) {
if (window.performance.now() - now >= interval) {
window.removeEventListener('message', handleMessage);
if (setTimeouts[index].active) {
setTimeouts[index]();
}
} else {
window.postMessage(index, '*');
}
}
};
window.addEventListener('message', handleMessage);
window.postMessage(index, '*');
return index;
}
export function customClearTimeout(setTimeoutId) {
if (setTimeouts[setTimeoutId]) {
setTimeouts[setTimeoutId].active = false;
}
}
const setIntervals = [];
export function customSetInterval(cb, interval) {
const intervalId = setIntervals.length;
setIntervals[intervalId] = function () {
if (setIntervals[intervalId].active) {
cb();
customSetTimeout(setIntervals[intervalId], interval);
}
};
setIntervals[intervalId].active = true;
customSetTimeout(setIntervals[intervalId], interval);
return intervalId;
}
export function customClearInterval(intervalId) {
if (setIntervals[intervalId]) {
setIntervals[intervalId].active = false;
}
}
Hi you can try this. ]
HOpe it will help. Thanks
function customSetTimeOut (callback, ms) {
var dt = new Date();
var i = dt.getTime();
var future = i + ms;
while(Date.now() <= future) {
//do nothing - blocking
}
return callback();
}
customSetTimeOut(function(){
console.log("Timeout success");
},1000);
Related
my question has been asked once here:
clearInterval in webworker is not working
The solution seems clear but for some reason it is not working for me yet. I have a web worker that is sending an interval back to the main thread. I want to be able to stop the interval with clearInterval, but it is not working.
I have it set up exactly the same as it suggests in the previous question, but still no luck. I have added some console.logs to verify I'm in the correct block. "Stop" logs to the console when it supposed to, but the timer doesn't stop posting to the main thread.
Can anyone spot what's going on here?
Thanks
worker.js
let mytimer;
self.onmessage = function(evt) {
if (evt.data == "start") {
console.log("start")
var i = 0;
mytimer = setInterval(function() {
i++;
postMessage(i);
}, 1000);
} else if (evt.data == "stop") {
console.log("stop")
clearInterval(mytimer);
}
};
Then I'm calling this from my React hook when timer.time is above or below a certain value (2000 in this case)
main.js
const worker = new myWorker()
useEffect(() => {
worker.addEventListener('message', function(e) {
//from interval in the worker
console.log('Message from Worker: ' + e.data);
})
if(timer.time > 2000){
worker.postMessage("start")
}else{
worker.postMessage("stop")
}
},[timer.time])
You should also clear the interval when you start a new interval. If you don't do it, your previous interval would keep running, and you'll lose the ability to clear it:
let mytimer;
self.onmessage = function(evt) {
console.log(evt.data)
if(evt.data === 'start' || evt.data === 'stop') {
clearInterval(mytimer);
}
if (evt.data == "start") {
var i = 0;
mytimer = setInterval(function() {
i++;
postMessage(i);
}, 1000);
}
};
You should create a single instance of the worker, and store it as ref:
const worker = useRef()
useEffect(() => {
worker.current = new myWorker()
return () => {
worker.current.terminate();
}
}, [])
Not related, but in addition, the useEffect adds a new event listener whenever timer.time changes, without clearing the previous one. I would split this into 2 useEffect blocks, one for sending (which can be combind with the creation of the worker), and the other for receiving.
useEffect(() => {
const eventHander = e => {
//from interval in the worker
console.log('Message from Worker: ' + e.data);
}
worker.current.addEventListener('message', eventHander)
return () => {
worker.current.removeEventListener('message', eventHander)
}
}, [])
useEffect(() => {
worker.current.postMessage(timer.time > 2000 ? 'start' : 'stop')
}, [timer.time])
I'm not sure about web workers but I am very familiar with using intervals in useEffect. Since useEffect is called everytime your dependencies change (timer.time), you need to store the interval in a useRef unless you are going to be clearing it before your dependency next changes
My question has two parts
I want to terminate a function if it runs more than 200ms and return a default value. I tried to experiment with the concept of Promise.race but that did not yield the desired result
I want to identify if the function has an infinite loop similiar to how JSFiddle or Codepen is able to exit when someone uses an infinite loop without freezing the browser or overloading the CPU
Is it possible
Yeah, you can do that, here's a general-purpose script I wrote as proof of concept. It dynamically creates a web worker and uses it in the same way other languages have threads. However, if you're just trying to kill an active XHR or Fetch call, there are built-in methods for doing that.
/**
* Run some code in it's own thread
* #params - Any parameters you want to be passed into the thread
* #param - The last parameter must be a function that will run in it's own thread
* #returns - An promise-like object with a `then`, `catch`, and `abort` method
*/
function Thread() {
var worker;
const promise = new Promise((resolve, reject) => {
var args = Array.from(arguments);
var func = args.pop();
if (typeof func !== "function") throw new Error("Invalid function");
var fstr = func.toString();
var mainBody = fstr.substring(fstr.indexOf("{") + 1, fstr.lastIndexOf("}"));
var paramNames = fstr.substring(fstr.indexOf("(") + 1, fstr.indexOf(")")).split(",").map(p => p.trim());
var doneFunct = paramNames.pop();
if (paramNames.length !== args.length) throw new Error("Invalid number of arguments.");
var workerStr = `var ${doneFunct} = function(){
var args = Array.from(arguments);
postMessage(args);
};
self.onmessage = function(d){
var [${paramNames.join(", ")}] = d.data;
${mainBody}
};`;
var blob = new Blob([workerStr], {
type: 'application/javascript'
});
worker = new Worker(URL.createObjectURL(blob));
worker.onerror = reject;
worker.onmessage = (d) => {
resolve(...d.data);
worker.terminate();
};
worker.postMessage(args);
});
return {
then: (...params)=>promise.then(...params),
catch: (...params)=>promise.catch(...params),
abort: ()=>worker.terminate()
}
}
////////////////////////
//// EXAMPLE USAGE /////
////////////////////////
// the thread will take 2 seconds to execute
// and then log the result
var myThread = new Thread("this is a message", 2, function(message, delaySeconds, exit) {
setTimeout(() => {
exit(message.split('').reverse().join(''));
}, delaySeconds * 1000);
});
myThread.then(result => console.log(result));
// the thread will take 2 seconds to execute
// but we will cancel it after one second
var myThread = new Thread("this is a message", 2, function(message, delaySeconds, exit) {
setTimeout(() => {
exit(message.split('').reverse().join(''));
}, delaySeconds * 1000);
});
setTimeout(()=>{
myThread.abort();
}, 1000);
If you have access to the function you can add a time check inside the loop that runs for long time and return when you reach time limit (simplest way, most compatible way).
If you don't know what the function is doing or can't modify it, you can try wrapping it inside a Web worker. If you start the long running js inside the worker, you can then .terminate() it when it reaches the timeout
Also, It is impossible to determine if something contains an infinite loop without executing it
Im reciving websocket messages on my webpage, and i'm trying to calculate the frequency at which they are received. I do this like so:
let startTime = new Date();
ws.onmessage = function (evt)
{
prevData = recivedData;
var receivedMsg = evt.data;
recivedData = JSON.parse(receivedMsg);
const endTime = new Date();
const timeDif = endTime - startTime;
startTime = endTime;
console.log(timeDif)
}
When I take a look at the console to see what it has printed, I see that the frequency is mostly around 60 ms (as expected). Although, every fourth time or so, it will be 0 ms, I find this unlikely and I cant find what is causing it. Any ideas on how to fix this issue?
ws.onmessage = async () => {
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
await wait(50); // await between executions
// do your code
}
Update:
See this sample code below. You said that you have some loop function. Maybe it will be helpful you if you have access to the event queue every cycle and never have more than one event in the queue.
let done = false;
setTimeout(() => {
done = true
}, 5);
const eventLoopQueue = () => {
return new Promise(resolve =>
setImmediate(() => {
console.log('event loop');
resolve();
})
);
}
const run = async () => {
while (!done) {
console.log('loop');
await eventLoopQueue();
}
}
run().then(() => console.log('Done'));
What happens here. You call eventLoopQueue() function every cycle. This function pushes the callback to the queue by setImmidiate(). The callback is executed right away and anything that is in the queue are going to be executed as well. So every cycle you clear up the queue. And I believe it will help you.
My understanding is that an entire array is pushed to a subscriber, unlike say an interval observer that can be unsubscribed/cancelled.
For example the following cancellation works...
// emit a value every second for approx 10 seconds
let obs = Rx.Observable.interval(1000)
.take(10)
let sub = obs.subscribe(console.log);
// but cancel after approx 4 seconds
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 4000);
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
However, replacing the interval with an array doesn't.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray)
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 1);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
Does each element need to be made asynchronous somehow, for example by wrapping it in setTimeout(..., 0)? Perhaps I've been staring at this problem too long and I'm totally off course in thinking that the processing of an array can be cancelled?
When using from(...) on an array all of the values will be emitted synchronously which doesn't allow any execution time to be granted to the setTimeout that you are using to unsubscribe. Infact, it finishes emitting before the line for the setTimeout is even reached. To allow the emits to not hog the thread you could use the async scheduler (from(..., Rx.Scheduler.async)) which will schedule work using setInterval.
Here are the docs: https://github.com/ReactiveX/rxjs/blob/master/doc/scheduler.md#scheduler-types
Here is a running example. I had to up the timeout to 100 to allow more room to breath. This will slow down your execution of-course. I don't know the reason that you are attempting this. We could probably provide some better advice if you could share the exact use-case.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray, Rx.Scheduler.async);
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 100);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
I've marked #bygrace's answer correct. Much appreciated! As mentioned in the comment to his answer, I'm posting a custom implementation of an observable that does support such cancellation for interest ...
const observable = stream => {
let timerID;
return {
subscribe: observer => {
timerID = setInterval(() => {
if (stream.length === 0) {
observer.complete();
clearInterval(timerID);
timerID = undefined;
}
else {
observer.next(stream.shift());
}
}, 0);
return {
unsubscribe: () => {
if (timerID) {
clearInterval(timerID);
timerID = undefined;
observer.cancelled();
}
}
}
}
}
}
// will count to 9999 in the console ...
let largeArray = [...Array(9999).keys()];
let obs = observable(largeArray);
let sub = obs.subscribe({
next: a => console.log(a),
cancelled: () => console.log('cancelled')
});
// except I cancel it here
setTimeout(sub.unsubscribe, 200);
Background
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);
Problem
The problem here is that all these calls are made at exactly the same time, thus overloading the resources that asyncMoc is using.
Objective
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:
Using Promises
Using setInterval
Using Promises
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.
Using setInterval
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.
Questions:
Which approach is better to achieve my objective and how can it be fixed?
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"}
Introduction
After reading both solutions, I have to say I am very thankful to both people who took their time to help me. It is moments like this (although rare) that make me proud of having a StackOverflow account.
This said, after reading both proposals, I came with one of my own, and I will explain which one I think is best and why.
My solution
My solution is based on #Arg0n's proposal, and it is a simplification/re-implementation of his code using the factory pattern in JavaScript and defended by Douglas Crockford, using ECMA6 features:
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);
Why am I posting this extra solution? Because I think it is a vast improvement over Arg0n's proposal, for the following reasons:
No falsiness. Falsy values and expressions (like !executing) are a problem in JavaScript. I strongly recommend Appendix A: Awful parts of JavaScript.
Implements catch should the asyncMock fail
Use of Array.prototype.shift instead of Array.prototype.splice which is easier to read and improves performance.
No use of new keyword, no messing of the this reference
No inner functions. ESlint will thank you :P
Use of factories Douglas Crockford style
If you liked Arg0n's solution, I recommend you have a look at mine.
#Arg0n VS #T.J. Crowder ... FIGHT!
Which solution is better and why?
At first i was inclined to Arg0n's solution, because it took inspiration from one of my failed attempts and made it work. On itself, that is remarkable.
Furthermore, Timers in JavaScript have precision issues, and JavaScript also has issues when making computations with numbers (check 0.1 + 0.2 !== 0.3).
However, both solution use Timers. In fact, you need timers to achieve this behavior. Furthermore #T.J. Crowder's solution does not do arithmetic with floating points, but whole numbers, so his calculations are safe and sound.
One could point out that the Math library was a mistake in JavaScript imported from java, but honestly that is going to far and there is nothing wrong with it.
Furthermore, because T.J.'s solution does not have a data structure like Arg0n's solution has, its code is smaller as it encompasses less logic to maintain. There is no question from a technical point of view, his solution is the one to go for, in this specific case.
However, for those of you who don't master the math behind, Arg0n's avenue is a pretty solid one.
Conclusion
From a technical point of view, T.J.'s solution wins. However I can say that I enjoyed Arg0n's solution a lot, and specially my version of his post, which is the one I am likely to use.
I hope this post helps someone in the future !