ping pong behaviour using deffered - javascript

I have created following javascript code:
var index, _i;
var dfd = new $.Deferred();
dfd.resolve();
for (index = _i = 0; _i < 10; index = ++_i) {
$.when($, dfd).done(function() {
console.log('ping');
promise = (function(index) {
setTimeout(function() {
console.log('pong');
dfd.resolve();
}, 3000);
dfd.reject();
})(index);
})
}
in console I see following result
ping
ping
ping
ping
ping
ping
ping
ping
ping
ping
pong
pong
pong
pong
pong
pong
pong
pong
pong
pong
But I want to achieve
ping
pong
ping
pong
....
Where do I wrong and how to rewrite code acording my desired behavour?

Here's a way to do it:
function go(val, t) {
var def = $.Deferred();
setTimeout(function() {
log(val);
def.resolve();
}, t);
return def.promise();
}
function run(numCycles, values, delay) {
function next() {
if (numCycles > 0) {
go(values[0], delay).then(function() {
return go(values[1], delay);
}).then(function() {
--numCycles;
next();
});
}
}
next();
}
run(10, ["ping", "pong"], 500);
Working demo: http://jsfiddle.net/jfriend00/g0Lxm3ws/
Conceptually, here's how this works:
The function go() outputs one message after a timer. It returns a promise that is resolved when the message has been output.
The function run() accepts a number of cycles and an array of two values to alternate between.
The function next() checks to see if there are any more cycles to run and if so, chains two go() operations together and when the last one is done, decrements the number of cycles and then calls next() again.
There are all sorts of issues with your implementation:
A given deferred or promise can only be resolved or rejected once.
$.when() takes a list of promises so $.when($, dfd) is just wrong.
$.when() is not even needed when there is only one promise as you can just use .then() directly on the promise.
Your for loop won't pause between promises so all your "ping" messages will come out immediately and then the "pong" messages will come out later.
FYI, if you want to cycle through an arbitrary array of values, you can do so like this:
function go(val, t) {
var def = $.Deferred();
setTimeout(function() {
log(val);
def.resolve();
}, t);
return def.promise();
}
function run(numCycles, values, delay) {
function next() {
if (numCycles > 0) {
// create initial resolved promise
// for start of a .reduce() chain
var d = $.Deferred().resolve().promise();
values.reduce(function(p, val){
return p.then(function() {
return go(val, delay);
});
}, d).then(function() {
--numCycles;
next();
});
}
}
next();
}
run(5, ["tic", "tac", "toe"], 500);
Working demo: http://jsfiddle.net/jfriend00/1ckb6sg6/

I must say I've no idea how you've arrived at that sort of code or what you're really trying to achieve. However, to at least answer the first of your questions (what's going wrong?):
Once a deferred is resolved, it cannot (I believe) later be rejected. They are intended as one-time-use objects, as discussed in this question.
Secondly, you are getting all the pings before the pongs because the mechanism by which the pings are output is a synchronous one (the loop). The mechanism by which the pongs are output, however, is an asynchronous one - the timeout. In effective terms, therefore, the pings are all output in the same procedural instance, whereas the pongs don't kick in till 3 seconds later - long after the pings have been output.

Related

Force stop function execution [duplicate]

How to implement a timeout in Javascript, not the window.timeout but something like session timeout or socket timeout - basically - a "function timeout"
A specified period of time that will be allowed to elapse in a system
before a specified event is to take place, unless another specified
event occurs first; in either case, the period is terminated when
either event takes place.
Specifically, I want a javascript observing timer that will observe the execution time of a function and if reached or going more than a specified time then the observing timer will stop/notify the executing function.
Any help is greatly appreciated! Thanks a lot.
I'm not entirely clear what you're asking, but I think that Javascript does not work the way you want so it cannot be done. For example, it cannot be done that a regular function call lasts either until the operation completes or a certain amount of time whichever comes first. That can be implemented outside of javascript and exposed through javascript (as is done with synchronous ajax calls), but can't be done in pure javascript with regular functions.
Unlike other languages, Javascript is single threaded so that while a function is executing a timer will never execute (except for web workers, but they are very, very limited in what they can do). The timer can only execute when the function finishes executing. Thus, you can't even share a progress variable between a synchronous function and a timer so there's no way for a timer to "check on" the progress of a function.
If your code was completely stand-alone (didn't access any of your global variables, didn't call your other functions and didn't access the DOM in anyway), then you could run it in a web-worker (available in newer browsers only) and use a timer in the main thread. When the web-worker code completes, it sends a message to the main thread with it's results. When the main thread receives that message, it stops the timer. If the timer fires before receiving the results, it can kill the web-worker. But, your code would have to live with the restrictions of web-workers.
Soemthing can also be done with asynchronous operations (because they work better with Javascript's single-threaded-ness) like this:
Start an asynchronous operation like an ajax call or the loading of an image.
Start a timer using setTimeout() for your timeout time.
If the timer fires before your asynchronous operation completes, then stop the asynchronous operation (using the APIs to cancel it).
If the asynchronous operation completes before the timer fires, then cancel the timer with clearTimeout() and proceed.
For example, here's how to put a timeout on the loading of an image:
function loadImage(url, maxTime, data, fnSuccess, fnFail) {
var img = new Image();
var timer = setTimeout(function() {
timer = null;
fnFail(data, url);
}, maxTime);
img.onLoad = function() {
if (timer) {
clearTimeout(timer);
fnSuccess(data, img);
}
}
img.onAbort = img.onError = function() {
clearTimeout(timer);
fnFail(data, url);
}
img.src = url;
}
My question has been marked as a duplicate of this one so I thought I'd answer it even though the original post is already nine years old.
It took me a while to wrap my head around what it means for Javascript to be single-threaded (and I'm still not sure I understood things 100%) but here's how I solved a similar use-case using Promises and a callback. It's mostly based on this tutorial.
First, we define a timeout function to wrap around Promises:
const timeout = (prom, time, exception) => {
let timer;
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, exception))
]).finally(() => clearTimeout(timer));
}
This is the promise I want to timeout:
const someLongRunningFunction = async () => {
...
return ...;
}
Finally, I use it like this.
const TIMEOUT = 2000;
const timeoutError = Symbol();
var value = "some default value";
try {
value = await timeout(someLongRunningFunction(), TIMEOUT, timeoutError);
}
catch(e) {
if (e === timeoutError) {
console.log("Timeout");
}
else {
console.log("Error: " + e);
}
}
finally {
return callback(value);
}
This will call the callback function with the return value of someLongRunningFunction or a default value in case of a timeout. You can modify it to handle timeouts differently (e.g. throw an error).
You could execute the code in a web worker. Then you are still able to handle timeout events while the code is running. As soon as the web worker finishes its job you can cancel the timeout. And as soon as the timeout happens you can terminate the web worker.
execWithTimeout(function() {
if (Math.random() < 0.5) {
for(;;) {}
} else {
return 12;
}
}, 3000, function(err, result) {
if (err) {
console.log('Error: ' + err.message);
} else {
console.log('Result: ' + result);
}
});
function execWithTimeout(code, timeout, callback) {
var worker = new Worker('data:text/javascript;base64,' + btoa('self.postMessage((' + String(code) + '\n)());'));
var id = setTimeout(function() {
worker.terminate();
callback(new Error('Timeout'));
}, timeout);
worker.addEventListener('error', function(e) {
clearTimeout(id);
callback(e);
});
worker.addEventListener('message', function(e) {
clearTimeout(id);
callback(null, e.data);
});
}
I realize this is an old question/thread but perhaps this will be helpful to others.
Here's a generic callWithTimeout that you can await:
export function callWithTimeout(func, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("timeout")), timeout)
func().then(
response => resolve(response),
err => reject(new Error(err))
).finally(() => clearTimeout(timer))
})
}
Tests/examples:
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const func1 = async () => {
// test: func completes in time
await sleep(100)
}
const func2 = async () => {
// test: func does not complete in time
await sleep(300)
}
const func3 = async () => {
// test: func throws exception before timeout
await sleep(100)
throw new Error("exception in func")
}
const func4 = async () => {
// test: func would have thrown exception but timeout occurred first
await sleep(300)
throw new Error("exception in func")
}
Call with:
try {
await callWithTimeout(func, 200)
console.log("finished in time")
}
catch (err) {
console.log(err.message) // can be "timeout" or exception thrown by `func`
}
You can achieve this only using some hardcore tricks. Like for example if you know what kind of variable your function returns (note that EVERY js function returns something, default is undefined) you can try something like this: define variable
var x = null;
and run test in seperate "thread":
function test(){
if (x || x == undefined)
console.log("Cool, my function finished the job!");
else
console.log("Ehh, still far from finishing!");
}
setTimeout(test, 10000);
and finally run function:
x = myFunction(myArguments);
This only works if you know that your function either does not return any value (i.e. the returned value is undefined) or the value it returns is always "not false", i.e. is not converted to false statement (like 0, null, etc).
Here is my answer which essentially simplifies Martin's answer and is based upon the same tutorial.
Timeout wrapper for a promise:
const timeout = (prom, time) => {
const timeoutError = new Error(`execution time has exceeded the allowed time frame of ${time} ms`);
let timer; // will receive the setTimeout defined from time
timeoutError.name = "TimeoutErr";
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, timeoutError)) // returns the defined timeoutError in case of rejection
]).catch(err => { // handle errors that may occur during the promise race
throw(err);
}) .finally(() => clearTimeout(timer)); // clears timer
}
A promise for testing purposes:
const fn = async (a) => { // resolves in 500 ms or throw an error if a == true
if (a == true) throw new Error('test error');
await new Promise((res) => setTimeout(res, 500));
return "p2";
}
Now here is a test function:
async function test() {
let result;
try { // finishes before the timeout
result = await timeout(fn(), 1000); // timeouts in 1000 ms
console.log('• Returned Value :', result, '\n'); // result = p2
} catch(err) {
console.log('• Captured exception 0 : \n ', err, '\n');
}
try { // don't finish before the timeout
result = await timeout(fn(), 100); // timeouts in 100 ms
console.log(result); // not executed as the timeout error was triggered
} catch (err) {
console.log('• Captured exception 1 : \n ', err, '\n');
}
try { // an error occured during fn execution time
result = await timeout(fn(true), 100); // fn will throw an error
console.log(result); // not executed as an error occured
} catch (err) {
console.log('• Captured exception 2 : \n ', err, '\n');
}
}
that will produce this output:
• Returned Value : p2
• Captured exception 1 :
TimeoutErr: execution time has exceeded the allowed time frame of 100 ms
at C:\...\test-promise-race\test.js:33:34
at async test (C:\...\test-promise-race\test.js:63:18)
• Captured exception 2 :
Error: test error
at fn (C:\...\test-promise-race\test.js:45:26)
at test (C:\...\test-promise-race\test.js:72:32)
If you don't want to use try ... catch instructions in the test function you can alternatively replace the throw instructions in the catch part of the timeout promise wrapper by return.
By doing so the result variable will receive the error that is throwed otherwise. You can then use this to detect if the result variable actually contains an error.
if (result instanceof Error) {
// there was an error during execution
}
else {
// result contains the value returned by fn
}
If you want to check if the error is relative to the defined timeout you will have to check the error.name value for "TimeoutErr".
Share a variable between the observing timer and the executing function.
Implement the observing timer with window.setTimeout or window.setInterval. When the observing timer executes, it sets an exit value to the shared variable.
The executing function constantly checks for the variable value.. and returns if the exit value is specified.

Promises inside of setInterval

I've got this routine function that runs every 60000ms on a setInterval function. Inside of this routine function, I loop over all the usernames on a parsed JSON(db) and check whether or not they're available via a promise (checkUsername), which is a network request.
However, It's pretty clear to me that this is a terrible approach because the promises may take more than 60 seconds to complete, and I've been getting ETIMEDOUT errors all over the place. But I just don't understand promises and asynchrony enough to think of a solution.
What would be a better approach to this?
Would async/await fit here?
function routine() {
db.users.forEach(userObj => {
userObj.username.forEach(username => {
checkUsername(username).then(hasUser => {
if(!hasUser) {
bot.sendMessage(userObj.chatId, `‼️ Username #${username} is now AVAILABLE ‼️`);
removeName(username, userObj.chatId);
}
}).catch(err => {
console.log(err);
})
})
});
}
setInterval(routine, 120000);
var interval = 60000;
function routine() {
return Promise.all(db.users.map(userObj => {
return Promise.all(userObj.username.map(username => {
return checkUsername(username).then(hasUser => {
if(!hasUser){
return removeName(username, userObj.chatId).then(function(){
return bot.sendMessage(userObj.chatId, `‼️ Username #${username} is now AVAILABLE ‼️`)
})
}
}))
})).then(function(){
setTimeout(routine, interval);
}, function(error){
console.error(error);
setTimeout(routine, interval);
})
}
routine();
This will run every 60 seconds plus the time it takes to resolve all the requests. If it fails, it will run again in 60 seconds. You could be running into timeout issues if you have too many users. Also, if "removeName" fails, "bot.sendMessage" won't run.
You can end promises many different ways. It depends on what you need to do with the promise.
.then(function(){
//do stuff
setTimeout(routine, interval);
}, function(error){
console.error(error);
setTimeout(routine, interval);
})
or
.then(function(){
//do stuff
setTimeout(routine, interval);
}).catch(function(error){
console.error(error);
setTimeout(routine, interval);
})
or
.catch(function(error){
console.error(error);
}).finally(function(){
setTimeout(routine, interval);
})
or
.finally(function(){
setTimeout(routine, interval);
})
I made a code snippet that you can run that makes use of Promise.all as well as async/await ES7 to make your code a bit easier to handle and understand. I'm also hitting a real API endpoint I found online just for the sake of a complete example.
I've also added a way to stop the timeout for good if you ever want that option.
// How often the timeout will run.
// Since the timeout is dependent on when all the requests finish, the timeout will run this many ms after all the requests finish.
var interval = 5000;
// This is what your db seems to resemble.
var db = {
users: [{
username: ['1']
},
{
username: ['2']
},
{
username: ['3']
},
{
username: ['4']
},
]
};
// This will hold the timeout function so you can cancel it at a later time.
var timeoutFn;
// Returns returns a single Promise that resolves when all of the promises it contains have resolved/rejected. It rejects with the first promise that rejects.
function routine() {
console.log("-- Running routine function --");
// Return an array of promises. Please see my comments at the bottom of this whole answer which questions db architecture with username being an array.
// I'm also using map instead of forEach because map automatically returns and array.
let promiseArray = db.users.map(userObj => {
return Promise.all(userObj.username.map(username => {
// This processUsername() function should do all the work related to the username. It helps to keep the routine function as clean as possible since there's already a lot happening in here.
return processUsername(username);
}));
});
// Returns an array of array of promises. This means that every single promise within each array (see above) has to resolve before the `then` runs. If any reject, `catch` will run instead.
return Promise.all(promiseArray).then(() => {
runRoutineAgain();
}).catch((err) => {
console.log('err:', err)
});
}
// This will create a timeout and run routine after interval.
function runRoutineAgain() {
timeoutFn = setTimeout(routine, interval);
}
// This async function returns a promise
async function processUsername(username) {
// Make API call to get data
console.log('Processing username for', username);
// I'm using this free API endpoint online just for the sake of making a complete example. Obviously your API will be returning very different data.
return await fetch(`https://jsonplaceholder.typicode.com/todos/${username}`)
.then(response => response.json())
.then((json) => {
console.log(json);
// This is where you can do your processing.
// if(!hasUser) {
// bot.sendMessage(userObj.chatId, `‼️ Username #${username} is now AVAILABLE ‼️`);
// removeName(username, userObj.chatId);
// }
});
}
function stopTimeout() {
clearTimeout(timeoutFn);
}
routine();
Basically, I'm using Promise.all to capture and wait on the result of individual promises which is very useful since you have many users you need to get data for.
Feel free to open up Web Console to better see the output data.
I'm also using async/await ES7 syntax just to demonstrate other (easier, some might say) ways to write Promises. I understand Promises can be daunting so here are some links that really hit the nail on the head when trying to learn them.
https://javascript.info/promise-basics - This covers Promises
https://javascript.info/async-await - This covers async/await
Also is there a reason why you are looping over each username for a user in your original code?
db.users.forEach(userObj => {
userObj.username.forEach(username => {…
If there’s only 1 username for a userObj, that second loop adds unnecessary complexity. But if your db has multiple usernames for a single userObj, then that’s completely fine!
One way is to clear the interval as soon as you start handling the interval-function. Then when your interval-function is done, you can start the interval again. This way your interval is not ticking while your interval-function is being executed. Take a look at below code.
let interval = setInterval(routine, 1000)
function routine() {
let sequence = Promise.resolve()
sequence
.then(function () {
clearInterval(interval)
return Promise.resolve()
})
// ... chain rest of your promise here.
.then(function (value) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('I take long time.');
resolve()
}, 5000)
})
})
.then(function () { // in last promise, start the timer again.
interval = setInterval(routine, 1000)
})
}
Here you interval-function is supposed to run every 1s, but the promise itself takes 5s. So the first thing the method does is stop the timer, then do its job, and finally re-enable the timer.
With timeout,
let timeout = setTimeout(routine, 1000)
function routine() {
let sequence = Promise.resolve()
sequence
.then(function () {
return Promise.resolve()
})
// ... chain rest of your promise here.
.then(function (value) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('I take long time.');
resolve()
}, 5000)
})
})
.then(function () { // in last promise, start the timer again.
timeout = setTimeout(routine, 1000)
})
}

AngularJS $q.all - wait between http calls

So I have a situation where I need to perform a bunch of http calls, then once they are complete, continue on to the next step in the process.
Below is the code which does this and works fine.
However, I now need to wait a few seconds between each of the http calls. Is there a way to pass in a timeout with my current set up, or will it involve a good bit of refactoring?
Can post more code if needs be. I have tried passing in a timeout config varable into the http call, however, they still get fired at the same time.
Any advice would be great.
Code
var allThings = array.map(function(object) {
var singleThingPromise = getFile(object.id);
return singleThingPromise;
});
$q.all(allThings).then(function() {
deferred.resolve('Finished');
}, function(error) {
deferred.reject(error);
});
Instead of using $q.all, you might want to perform sequential calls one on success of previous and probably with use of $timeout. Maybe you could build a recursive function.
Something like this..
function performSequentialCalls (index) {
if(angular.isUndefined(array[index])) {
return;
}
getFile(array[index].id).then(function() {
$timeout(function() {
performSequentialCalls(index + 1)
}, 1000) // waiting 1 sec after each call
})
}
Inject required stuff properly. This assumes array to contain objects with ids using which you perform API calls. Also assumes that you are using $http. If using $resource, add $promise accordingly.
Hope that helps a bit!
function getItemsWithDelay(index) {
getFile(object[index].id).then(()=>{
setTimeout(()=>{
if(index+1 > object.length) { return }
getItemsWithDelay(index+1)
}, 5000)
})
}
You can make sequential calls
This is a awesome trick question to be asked in an interview, anyways I had a similar requirement and did some research on the internet and thanks to reference https://codehandbook.org/understanding-settimeout-inside-for-loop-in-javascript
I was able to delay all promise call in angularjs and the same can be applied in normal JS syntax as well.
I need to send tasks to a TTP API, and they requested to add a delay in each call
_sendTasks: function(taskMeta) {
var defer = $q.defer();
var promiseArray = [];
const delayIncrement = 1000 * 5;
let delay = 0;
for (i = 0; i < taskMeta.length; i++) {
// using 'let' keyword is VERY IMPORTANT else 'var' will send the same task in all http calls
let requestTask = {
"action": "SOME_ACTION",
"userId": '',
"sessionId": '',
};
// new Promise can be replaced with $q - you can try that, I haven't test it although.
promiseArray.push(new Promise(() => setTimeout(() => $http.post(config.API_ROOT_URL + '/' + requestTask.action, requestTask), delay)));
delay += delayIncrement;
}
$q.all(promiseArray).
then(function(results) {
// handle the results and resolve it at the end
defer.resolve(allResponses);
})
.catch(error => {
console.log(error);
defer.reject("failed to execute");
});
return defer.promise;
}
Note:: using 'let' keyword in FOR loop is VERY IMPORTANT else 'var' will send the same task in all http calls - due to closure/context getting switched

Timed promise queue / throttle

I have a request-promise function that makes a request to an API. I'm rate-limited by this API and I keep getting the error message:
Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.
I'm running a couple of Promise.each loops in parallel which is causing the issue, if I run just one instance of Promise.each everything runs fine. Within these Promise.each calls they lead to the same function a with a request-promise call. I want to wrap this function with another queue function and set the interval to 500 milliseconds so that a request isn't made after one another, or parallel, but set to that time, on queue. The thing is I still need these promises to get their contents even if it takes a rather long time to get a response.
Is there anything that will do this for me? Something I can wrap a function in and it will respond at a set interval and not in parallel or fire functions one after another?
Update: Perhaps it does need to be promise specific, I tried to use underscore's throttle function
var debug = require("debug")("throttle")
var _ = require("underscore")
var request = require("request-promise")
function requestSite(){
debug("request started")
function throttleRequest(){
return request({
"url": "https://www.google.com"
}).then(function(response){
debug("request finished")
})
}
return _.throttle(throttleRequest, 100)
}
requestSite()
requestSite()
requestSite()
And all I got back was this:
$ DEBUG=* node throttle.js
throttle request started +0ms
throttle request started +2ms
throttle request started +0ms
Update
The last answer was wrong, this works but I still think I can do better:
// call fn at most count times per delay.
const debounce = function (fn, delay, count) {
let working = 0, queue = [];
function work() {
if ((queue.length === 0) || (working === count)) return;
working++;
Promise.delay(delay).tap(() => working--).then(work);
let {context, args, resolve} = queue.shift();
resolve(fn.apply(context, args));
}
return function debounced() {
return new Promise(resolve => {
queue.push({context: this, args: arguments, resolve});
if (working < count) work();
});
};
};
function mockRequest() {
console.log("making request");
return Promise.delay(Math.random() * 100);
}
var bounced = debounce(mockRequest, 800, 5);
for (var i = 0; i < 5; i++) bounced();
setTimeout(function(){
for (var i = 0; i < 20; i++) bounced();
},2000);
So you need to make the requests throttle function-wide - that's fine. Promises have queueing pretty much built in.
var p = Promise.resolve(); // our queue
function makeRequest(){
p = p.then(function(){ // queue the promise, wait for the queue
return request("http://www.google.com");
});
var p2 = p; // get a local reference to the promise
// add 1000 ms delay to queue so the next caller has to wait
p = p.delay(1000);
return p2;
};
Now makeRequest calls will be at least 1000ms apart.
jfriend has pointed out that you need two requests per second and not a single one - this is just as easily solvable with a second queue:
var p = Promise.resolve(1); // our first queue
var p2 = Promise.resolve(2); // our second queue
function makeRequest(){
var turn = Promise.any([p, p2]).then(function(val){
// add 1000 ms delay to queue so the next caller has to wait
// here we wait for the request too although that's not really needed,
// check both options out and decide which works better in your case
if(val === 1){
p = p.return(turn).delay(1, 1000);
} else {
p2 = p2.return(turn).delay(1, 1000);
}
return request("http://www.google.com");
});
return turn; // return the actual promise
};
This can be generalized to n promises using an array similarly

How to implement a "function timeout" in Javascript - not just the 'setTimeout'

How to implement a timeout in Javascript, not the window.timeout but something like session timeout or socket timeout - basically - a "function timeout"
A specified period of time that will be allowed to elapse in a system
before a specified event is to take place, unless another specified
event occurs first; in either case, the period is terminated when
either event takes place.
Specifically, I want a javascript observing timer that will observe the execution time of a function and if reached or going more than a specified time then the observing timer will stop/notify the executing function.
Any help is greatly appreciated! Thanks a lot.
I'm not entirely clear what you're asking, but I think that Javascript does not work the way you want so it cannot be done. For example, it cannot be done that a regular function call lasts either until the operation completes or a certain amount of time whichever comes first. That can be implemented outside of javascript and exposed through javascript (as is done with synchronous ajax calls), but can't be done in pure javascript with regular functions.
Unlike other languages, Javascript is single threaded so that while a function is executing a timer will never execute (except for web workers, but they are very, very limited in what they can do). The timer can only execute when the function finishes executing. Thus, you can't even share a progress variable between a synchronous function and a timer so there's no way for a timer to "check on" the progress of a function.
If your code was completely stand-alone (didn't access any of your global variables, didn't call your other functions and didn't access the DOM in anyway), then you could run it in a web-worker (available in newer browsers only) and use a timer in the main thread. When the web-worker code completes, it sends a message to the main thread with it's results. When the main thread receives that message, it stops the timer. If the timer fires before receiving the results, it can kill the web-worker. But, your code would have to live with the restrictions of web-workers.
Soemthing can also be done with asynchronous operations (because they work better with Javascript's single-threaded-ness) like this:
Start an asynchronous operation like an ajax call or the loading of an image.
Start a timer using setTimeout() for your timeout time.
If the timer fires before your asynchronous operation completes, then stop the asynchronous operation (using the APIs to cancel it).
If the asynchronous operation completes before the timer fires, then cancel the timer with clearTimeout() and proceed.
For example, here's how to put a timeout on the loading of an image:
function loadImage(url, maxTime, data, fnSuccess, fnFail) {
var img = new Image();
var timer = setTimeout(function() {
timer = null;
fnFail(data, url);
}, maxTime);
img.onLoad = function() {
if (timer) {
clearTimeout(timer);
fnSuccess(data, img);
}
}
img.onAbort = img.onError = function() {
clearTimeout(timer);
fnFail(data, url);
}
img.src = url;
}
My question has been marked as a duplicate of this one so I thought I'd answer it even though the original post is already nine years old.
It took me a while to wrap my head around what it means for Javascript to be single-threaded (and I'm still not sure I understood things 100%) but here's how I solved a similar use-case using Promises and a callback. It's mostly based on this tutorial.
First, we define a timeout function to wrap around Promises:
const timeout = (prom, time, exception) => {
let timer;
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, exception))
]).finally(() => clearTimeout(timer));
}
This is the promise I want to timeout:
const someLongRunningFunction = async () => {
...
return ...;
}
Finally, I use it like this.
const TIMEOUT = 2000;
const timeoutError = Symbol();
var value = "some default value";
try {
value = await timeout(someLongRunningFunction(), TIMEOUT, timeoutError);
}
catch(e) {
if (e === timeoutError) {
console.log("Timeout");
}
else {
console.log("Error: " + e);
}
}
finally {
return callback(value);
}
This will call the callback function with the return value of someLongRunningFunction or a default value in case of a timeout. You can modify it to handle timeouts differently (e.g. throw an error).
You could execute the code in a web worker. Then you are still able to handle timeout events while the code is running. As soon as the web worker finishes its job you can cancel the timeout. And as soon as the timeout happens you can terminate the web worker.
execWithTimeout(function() {
if (Math.random() < 0.5) {
for(;;) {}
} else {
return 12;
}
}, 3000, function(err, result) {
if (err) {
console.log('Error: ' + err.message);
} else {
console.log('Result: ' + result);
}
});
function execWithTimeout(code, timeout, callback) {
var worker = new Worker('data:text/javascript;base64,' + btoa('self.postMessage((' + String(code) + '\n)());'));
var id = setTimeout(function() {
worker.terminate();
callback(new Error('Timeout'));
}, timeout);
worker.addEventListener('error', function(e) {
clearTimeout(id);
callback(e);
});
worker.addEventListener('message', function(e) {
clearTimeout(id);
callback(null, e.data);
});
}
I realize this is an old question/thread but perhaps this will be helpful to others.
Here's a generic callWithTimeout that you can await:
export function callWithTimeout(func, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("timeout")), timeout)
func().then(
response => resolve(response),
err => reject(new Error(err))
).finally(() => clearTimeout(timer))
})
}
Tests/examples:
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const func1 = async () => {
// test: func completes in time
await sleep(100)
}
const func2 = async () => {
// test: func does not complete in time
await sleep(300)
}
const func3 = async () => {
// test: func throws exception before timeout
await sleep(100)
throw new Error("exception in func")
}
const func4 = async () => {
// test: func would have thrown exception but timeout occurred first
await sleep(300)
throw new Error("exception in func")
}
Call with:
try {
await callWithTimeout(func, 200)
console.log("finished in time")
}
catch (err) {
console.log(err.message) // can be "timeout" or exception thrown by `func`
}
You can achieve this only using some hardcore tricks. Like for example if you know what kind of variable your function returns (note that EVERY js function returns something, default is undefined) you can try something like this: define variable
var x = null;
and run test in seperate "thread":
function test(){
if (x || x == undefined)
console.log("Cool, my function finished the job!");
else
console.log("Ehh, still far from finishing!");
}
setTimeout(test, 10000);
and finally run function:
x = myFunction(myArguments);
This only works if you know that your function either does not return any value (i.e. the returned value is undefined) or the value it returns is always "not false", i.e. is not converted to false statement (like 0, null, etc).
Here is my answer which essentially simplifies Martin's answer and is based upon the same tutorial.
Timeout wrapper for a promise:
const timeout = (prom, time) => {
const timeoutError = new Error(`execution time has exceeded the allowed time frame of ${time} ms`);
let timer; // will receive the setTimeout defined from time
timeoutError.name = "TimeoutErr";
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, timeoutError)) // returns the defined timeoutError in case of rejection
]).catch(err => { // handle errors that may occur during the promise race
throw(err);
}) .finally(() => clearTimeout(timer)); // clears timer
}
A promise for testing purposes:
const fn = async (a) => { // resolves in 500 ms or throw an error if a == true
if (a == true) throw new Error('test error');
await new Promise((res) => setTimeout(res, 500));
return "p2";
}
Now here is a test function:
async function test() {
let result;
try { // finishes before the timeout
result = await timeout(fn(), 1000); // timeouts in 1000 ms
console.log('• Returned Value :', result, '\n'); // result = p2
} catch(err) {
console.log('• Captured exception 0 : \n ', err, '\n');
}
try { // don't finish before the timeout
result = await timeout(fn(), 100); // timeouts in 100 ms
console.log(result); // not executed as the timeout error was triggered
} catch (err) {
console.log('• Captured exception 1 : \n ', err, '\n');
}
try { // an error occured during fn execution time
result = await timeout(fn(true), 100); // fn will throw an error
console.log(result); // not executed as an error occured
} catch (err) {
console.log('• Captured exception 2 : \n ', err, '\n');
}
}
that will produce this output:
• Returned Value : p2
• Captured exception 1 :
TimeoutErr: execution time has exceeded the allowed time frame of 100 ms
at C:\...\test-promise-race\test.js:33:34
at async test (C:\...\test-promise-race\test.js:63:18)
• Captured exception 2 :
Error: test error
at fn (C:\...\test-promise-race\test.js:45:26)
at test (C:\...\test-promise-race\test.js:72:32)
If you don't want to use try ... catch instructions in the test function you can alternatively replace the throw instructions in the catch part of the timeout promise wrapper by return.
By doing so the result variable will receive the error that is throwed otherwise. You can then use this to detect if the result variable actually contains an error.
if (result instanceof Error) {
// there was an error during execution
}
else {
// result contains the value returned by fn
}
If you want to check if the error is relative to the defined timeout you will have to check the error.name value for "TimeoutErr".
Share a variable between the observing timer and the executing function.
Implement the observing timer with window.setTimeout or window.setInterval. When the observing timer executes, it sets an exit value to the shared variable.
The executing function constantly checks for the variable value.. and returns if the exit value is specified.

Categories

Resources