How to cancel JavaScript sleep? [duplicate] - javascript

This question already has answers here:
How to cancel timeout inside of Javascript Promise?
(4 answers)
Closed 3 months ago.
This function is used to wait for millis number of second.
function delay(millis: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, millis);
});
My goal is then to return the timeout out of this function?
const timeout = await delay(20000);
on click somewhere else, user bored of waiting
clearTimeout(timeout)

Simply extend the Promise object you'll be sending:
function delay( millis ) {
let timeout_id;
let rejector;
const prom = new Promise((resolve, reject) => {
rejector = reject;
timeout_id = setTimeout(() => {
resolve();
}, millis);
});
prom.abort = () => {
clearTimeout( timeout_id );
rejector( 'aborted' );
};
return prom;
}
const will_abort = delay( 2000 );
const will_not_abort = delay( 2000 );
will_abort
.then( () => console.log( 'will_abort ended' ) )
.catch( console.error );
will_not_abort
.then( () => console.log( 'will_not_abort ended' ) )
.catch( console.error );
setTimeout( () => will_abort.abort(), 1000 );

You can return the resolve / reject along with the promise
function delay(millis) {
let reolver;
return [new Promise((resolve, reject) => {
resolver = resolve
x = setTimeout(() => {
resolve();
}, millis);
}), resolver];
}
const [sleep1, wake1] = delay(2000)
sleep1.then((x) => console.log(x || 'wakeup 1')) // Auto wake after 2 seconds
const [sleep2, wake2] = delay(2000)
sleep2.then((x) => console.log(x || 'wakeup 2'))
wake2('Custom Wakeup') // sleep2 cancelled will wake from wake2 call

You can use an AbortSignal:
function delay(ms, signal) {
return new Promise((resolve, reject) => {
function done() {
resolve();
signal?.removeEventListener("abort", stop);
}
function stop() {
reject(this.reason);
clearTimeout(handle);
}
signal?.throwIfAborted();
const handle = setTimeout(done, ms);
signal?.addEventListener("abort", stop);
});
}
Example:
const controller = new AbortController()
delay(9000, controller.signal).then(() => {
console.log('Finished sleeping');
}, err => {
if (!controller.signal.aborted) throw err;
// alternatively:
if (err.name != "AbortError") throw err;
console.log('Cancelled sleep before it went over 9000')
});
button.onclick => () => {
controller.abort();
};

You can return a function which cancels the timer (cancelTimer) along with the Promise object as an array from the delay function call.
Then use the cancelTimer to clear it if required and which would also reject the Promise:
function delay(millis) {
let cancelTimer;
const promise = new Promise((resolve, reject) => {
const timeoutID = setTimeout(() => {
resolve("done");
}, millis);
cancelTimer = () => {
clearTimeout(timeoutID);
reject("Promise cancelled");
};
});
return [promise, cancelTimer];
}
//DEMO
let [promiseCancelled, cancelTimer] = delay(20000);
(async() => {
try {
console.log("Promise result never printed", await promiseCancelled);
} catch (error) {
console.error("Promise is rejected", error);
}
})();
cancelTimer();
const [promise, _] = delay(2000);
(async() => {
console.log("Promise result printed", await promise);
})();

I recommend that you do not modify the promise by attaching an .abort method to it. Instead return two values from your delay function
the delayed value
a function that can be called to cancel the timeout
Critically, we do not see "x" logged to the console because it was canceled -
function useDelay (x = null, ms = 1000)
{ let t
return [
new Promise(r => t = setTimeout(r, ms, x)), // 1
_ => clearTimeout(t) // 2
]
}
const [x, abortX] =
useDelay("x", 5000)
const [y, abortY] =
useDelay("y canceled x", 2000)
x.then(console.log, console.error)
y.then(console.log, console.error).finally(_ => abortX())
console.log("loading...")
// loading...
// y canceled x
interactive demo
Here's an interactive example that allows you to wait for a delayed value or abort it -
function useDelay (x = null, ms = 1000)
{ let t
return [
new Promise(r => t = setTimeout(r, ms, x)), // 1
_ => clearTimeout(t) // 2
]
}
const [ input, output ] =
document.querySelectorAll("input")
const [go, abort] =
document.querySelectorAll("button")
let loading = false
function reset ()
{ loading = false
input.value = ""
go.disabled = false
abort.disabled = true
}
function load ()
{ loading = true
go.disabled = true
abort.disabled = false
}
go.onclick = e => {
if (loading) return
load()
const [delayedValue, abortValue] =
useDelay(input.value, 3000)
abort.onclick = _ => {
abortValue()
reset()
}
delayedValue
.then(v => output.value = v)
.catch(e => output.value = e.message)
.finally(_ => reset())
}
code { color: dodgerblue; }
<input placeholder="enter a value..." />
<button>GO</button>
<button disabled>ABORT</button>
<input disabled placeholder="waiting for output..." />
<p>Click <code>GO</code> and the value will be transferred to the output in 5 seconds.</p>
<p>If you click <code>ABORT</code> the transfer will be canceled.</p>

Just use an AbortSignal:
/**
* Sleep for the specified number of milliseconds, or until the abort
* signal gets triggered.
*
* #param ms {number}
* #param signal {AbortSignal|undefined}
*/
const sleep = (ms, signal) =>
new Promise<void>((ok, ng) => {
/** #type {number} */
let timeout
const abortHandler = () => {
clearTimeout(timeout)
const aborted = new Error(`sleep aborted`)
aborted.name = 'AbortError'
ng(aborted)
}
signal?.addEventListener('abort', abortHandler)
timeout = setTimeout(() => {
signal?.removeEventListener('abort', abortHandler)
ok()
}, ms)
})
> const a = new AbortController()
undefined
> const s = sleep(900000, a.signal)
undefined
> a.abort()
undefined
> await s
Uncaught AbortError: sleep aborted
at AbortSignal.abortHandler
at innerInvokeEventListeners
at invokeEventListeners
at dispatch
at AbortSignal.dispatchEvent
at AbortSignal.[[[signalAbort]]]
at AbortController.abort
at <anonymous>:2:3
>
If you prefer not to fail the promise, i.e. treat the abort signal as "skip sleep", simply replace the call to ng(aborted) with a call to ok().

Related

Javascript cancel await for animation sequence?

I have a function like this:
const init = async () => {
const els = [...]; //array of html elements
els[0].classList.add('hidden');
await sleep(200);
els[1].classList.remove('hidden');
await sleep(500);
els[3].classList.remove('hidden');
await sleep(4000);
els[3].classList.add('hidden');
els[2].classList.remove('hidden');
els[1].classList.add('hidden');
await sleep(800);
els[3].classList.add('out');
els[4].classList.remove('hidden');
}
As you can see there's a 4 second await in there. I want to, using an external function that comes from a click, to be able to skip that 4000ms delay.
const cancelAnimation = () => {
// whatever
}
I thought of using a flag variable to change the number from 4000 to 500 for example, but if it already gotten into that sleep(4000) it doesn't matter cause the number won't change.
So, is there any way to cancel this out?
Btw, this is the code from the sleep function:
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
You can make your promise cancelable:
const cancelableSleep = (ms) => {
let timeout;
return {
promise: new Promise((resolve) => {
timeout = setTimeout(resolve, ms);
}),
cancel() {
clearTimeout(timeout);
},
};
};
const init = async () => {
const cancelable = cancelableSleep(10000);
//simulate click in 2 seconds
setTimeout(() => cancelable.cancel(), 2000);
console.log("sleeping");
await cancelable.promise;
console.log("awake");
};
init();
A bit of magic (https://codesandbox.io/s/green-dream-u2yxk?file=/src/index.js) :)
import CPromise from "c-promise2";
const init = () => CPromise.from(function* () {
let skip = false;
let promise;
this.on("signal", (type) => {
if (type === "skip") {
promise ? promise.cancel() : (skip = true);
}
});
console.log("stage1");
yield CPromise.delay(200);
console.log("stage2");
yield CPromise.delay(500);
console.log("stage3");
if (!skip) {
yield (promise = CPromise.delay(4000)).cancelled();
}
console.log("stage4");
yield CPromise.delay(800);
console.log("stage5");
});
const task = init();
console.log(task instanceof Promise); // true
setTimeout(() => {
task.emitSignal("skip");
}, 800);

How to repeatedly call asynchronous function until specified timeout?

I want to keep calling asnchronous api requests repeatedly until it exceeds specified time. Using async-retry we can only specify retrycount and interval, we wanted to specify even timeout in the parameter.
Can you just suggest a way?
// try calling apiMethod 3 times, waiting 200 ms between each retry
async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
// do something with the result
});
Here is what you want :
const scheduleTrigger = (futureDate) => {
const timeMS = new Date(futureDate) - new Date();
return new Promise((res, ref) => {
if (timeMS > 0) {
setTimeout(() => {
res();
}, timeMS);
} else {
rej();
}
})
}
//const futureDate = '2020-07-23T20:53:12';
// or
const futureDate = new Date();
futureDate.setSeconds(futureDate.getSeconds() + 5);
console.log('now');
scheduleTrigger(futureDate).then(_ => {
console.log('future date reached');
// start whatever you want
stopFlag = false;
}).catch(_ => {
// the date provided was in the past
});
const wait = (ms = 2000) => {
return new Promise(res => {
setTimeout(_ => {
res();
}, ms);
})
}
const asyncFn = _ => Promise.resolve('foo').then(x => console.log(x));
let stopFlag = true;
(async () => {
while (stopFlag) {
await asyncFn();
await wait();
}
})();
So you want to keep retrying for as long as its within a certain timeout? How about this:
// Allow retry until the timer runs out
let retry = true;
const timeout = setTimeout(() => {
// Set retry to false to disabled retrying
retry = false;
// Can also build in a cancel here
}, 10000); // 10 second timeout
const retryingCall = () => {
apiMethod()
.then(response => {
// Optionally clear the timeout
clearTimeout(timeout);
})
.catch(() => {
// If retry is still true, retry this function again
if (retry) {
retryingCall();
}
});
};
You can achieve what you want with this function:
const retryWithTimeout = ({ timeout, ...retryOptions}, apiMethod, callback) => {
let timedout = false;
const handle = setTimeout(
() => (timedout = true, callback(new Error('timeout'))),
timeout
);
return async.retry(
retryOptions,
innerCallback => timedout || apiMethod(innerCallback),
(err, result) => timedout || (clearTimeout(handle), callback(err, result))
)
};
It has the advantage of allowing you to use the functionality of async.retry, as you apparently want, and also allows the timeout to take place even when what exceeds the timeout is the apiMethod itself, not the waiting time.
Usage example:
retryWithTimeout(
{timeout: 305, times: 4, interval: 100},
(callback) => { callback('some api error'); },
(err, result) => console.log('result', err, result)
)

How to set a time limit to a method in NodeJs?

I have a use case, where I am doing an external API call from my code,
The response of the external API is required by my code further on
I am bumping into a scenario, where the external API call at times takes far too long to return a response,
casing my code to break, being a serverless function
So I want to set a time limit to the external API call,
Where if I don't get any response from it within 3 secs, I wish the code to gracefully stop the further process
Following is a pseudo-code of what I am trying to do, but couldn't figure out the logic -
let test = async () => {
let externalCallResponse = '';
await setTimeout(function(){
//this call sometimes takes for ever to respond, but I want to limit just 3secs to it
externalCallResponse = await externalCall();
}, 3000);
if(externalCallResponse != ''){
return true;
}
else{
return false;
}
}
test();
Reference -
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SSM.html#getParameters-property
I'm using AWS SSM's getParameters method
You cannot await setTimeout as it doesn't returns a Promise.
You could implement a function that returns a Promise which is fulfilled after 3 seconds.
function timeout(seconds) {
return new Promise((resolve) => {
setTimeout(resolve, seconds * 1000)
});
}
You can await the above function in your code passing the number of seconds you want to wait for
let test = async () => {
let externalCallResponse = '';
setTimeout(async () => {
externalCallResponse = await externalCall();
}, 0);
await timeout(3); // wait for 3 seconds
if(externalCallResponse != '') return true;
else return false;
}
Following code snippet demonstrates the usage of timeout function written above. It mocks a api request that returns a response after 4 seconds.
function timeout(seconds) {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
function apiRequest() {
return new Promise(resolve => {
setTimeout(() => resolve('Hello World'), 4000);
});
}
let test = async () => {
let externalCallResponse = '';
setTimeout(async () => {
externalCallResponse = await apiRequest();
}, 0);
await timeout(3); // wait for 3 seconds
if (externalCallResponse != '') return true;
else return false;
};
test()
.then(res => console.log(res))
.catch(err => console.log(err.message));
you can use something like this, I created a function that return a promise then I used this promise.
let test = async () => {
return promiseTimeout()
}
const promiseTimeout = () => {
return new Promise(async (resolve, reject) => {
setTimeout(function () {
let externalCallResponse=""
externalCallResponse = await externalCall();
if (externalCallResponse != '') {
return resolve(true);
}
else {
return resolve(false);
}
}, 3000);
})
}
test().then(result=>{
console.log(result);
});
You could do something like this:
const timeout = async (func, millis) => {
return new Promise(async (resolve, reject) => {
setTimeout(() => reject(), millis);
resolve(await func());
});
}
timeout(() => doStuff(), 3000)
.then(() => console.log('worked'))
.catch(() => console.log('timed out'));
Tests:
const timeout = async (func, millis) => {
return new Promise(async (resolve, reject) => {
setTimeout(() => reject(), millis);
resolve(await func());
});
}
const doStuffShort = async () => { // Runs for 1 second
return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
const doStuffLong = async () => { // Runs for 5 seconds
return new Promise((resolve) => setTimeout(() => resolve(), 5000));
}
timeout(() => doStuffShort(), 3000)
.then(() => console.log('1 worked'))
.catch(() => console.log('1 timed out'));
timeout(() => doStuffLong(), 3000)
.then(() => console.log('2 worked'))
.catch(() => console.log('2 timed out'));

Convert setInterval to promise

Hello I'm new to javascript and wondering if there is a way to covnert below setInterval thingy into a promise so that .then could be used instead of the callback. Any help?
My ideas:
With a setTimeout I could resolve after a fixed time. But I'm not getting any ideas dealing with setInterval...
function alert_above(scrip, price, callback) {
var intvl = setInterval(() => {
if (get_last_price(scrip) > price) {
callback();
clearInterval(intvl);
}
}, 1000);
return intvl;
}
You can create a promise function that resolves asynchronously. Read more about Promise Here
function myInterval() {
return new Promise(resolve => {
const intId = setInterval(() => {
clearInterval(intId);
resolve();
}, 1000)
})
}
myInterval().then(() => {
console.log('Called after 1 second');
})
I think you could wrap into a new Promise like :
function promisifySetInterval(time) {
var defer = new Promise(resolve => {
let counter = 0
var intvl = setInterval(() => {
if (counter > time) {
resolve('hey');
clearInterval(intvl);
} else {
counter += 1000
}
}, 1000);
})
return defer;
}
promisifySetInterval(2000).then(param => {
console.log('hey', param)
})
And for youre case something like this :
function alert_above(scrip, price) {
var defer = new Promise(resolve => {
var intvl = setInterval(() => {
if (get_last_price(scrip) > price) {
resolve('hey');
clearInterval(intvl);
}
}, 1000);
})
return defer;
}
alert_above().then(param => {
console.log('hey', param)
})
Note: poll will be executed without delay the first time, which is different from the native setInterval.
Q: Why is poll based on setTimeout not setInterval?
A: Please see Execute the setInterval function without delay the first time.
Implementation:
// Promisify setTimeout
const pause = (ms, cb, ...args) =>
new Promise((resolve, reject) => {
setTimeout(async () => {
try {
resolve(await cb?.(...args))
} catch (error) {
reject(error)
}
}, ms)
})
// Promisify setInterval
const poll = async (interval, times, cb, ...args) => {
let result
const resolve = value => (times = 0) || (result = value)
const reject = reason => (times = 0) || (result = Promise.reject(reason))
await (async function basePoll() {
if (times > 0) {
const _result = await cb(...args, resolve, reject)
if (times) {
result = _result
--times && (await pause(interval, basePoll))
}
}
})()
return result
}
Tests:
import ordinal from 'ordinal'
// Test 1
poll(1000, 3, (a, b, c) => [a, b, c], 1, 2, 3).then(value => console.log(value))
// Test 2
let times = 0
poll(1000, 5, resolve => {
console.log(`${ordinal(++times)} time`)
times === 3 && resolve('resolved')
}).then(value => console.log(value))
// Test 3
let times = 0
poll(1000, 5, (resolve, reject) => {
console.log(`${ordinal(++times)} time`)
times === 3 && reject('rejected')
}).catch(error => console.error(error))

"Lock" a block in async function from "concurency"

Even though javascript runs in single thread, concurency issues may still arise in async functions. Some of them may be avoided by greatly increasing the complexity of the code, but some I solve like this:
// private "lock"
let _lock = null;
// this function waits till the last call is done, then
// initiates next one
async function doTheStuff() {
while (_lock) {
await _lock;
}
_lock = actuallyDoTheStuff();
const result = await _lock;
_lock = null;
return result;
}
async function actuallyDoTheStuff() {
// this function really does the stuff
}
This ensures that only one instance of actuallyDoTheStuff is running, but it doesn't really look that nice.
Will this truly work? Can I be sure there will be no endless loop/lock?
And, whether it works or not, isn't there a better way to do this?
I'd encapsulate everything inside actuallyDoTheStuff, which simply calls .then on the last Promise it generated:
const actuallyDoTheStuff = (() => {
let lastProm = Promise.resolve();
return () => {
const nextProm = lastProm.then(() => {
return new Promise(resolve => setTimeout(() => {
console.log('resolving');
resolve();
}, 1000));
});
lastProm = nextProm;
return lastProm;
};
})();
console.log('start');
actuallyDoTheStuff();
actuallyDoTheStuff();
actuallyDoTheStuff();
setTimeout(() => {
actuallyDoTheStuff();
actuallyDoTheStuff();
}, 200);
If it may throw, then add a catch when reassigning to lastProm
const actuallyDoTheStuff = (() => {
let lastProm = Promise.resolve();
return () => {
const nextProm = lastProm.then(() => {
return new Promise(resolve => setTimeout(() => {
console.log('resolving');
resolve();
}, 1000));
});
lastProm = nextProm.catch(() => null);
return nextProm;
};
})();
console.log('start');
actuallyDoTheStuff();
actuallyDoTheStuff();
actuallyDoTheStuff();
setTimeout(() => {
actuallyDoTheStuff();
actuallyDoTheStuff();
}, 200);
I'm not sure exactly what actuallyDoTheStuff eventually should do, but if you're trying to sequence multiple calls of it (and await each call), you could make doTheStuff an async wrapper function with a for loop that awaits actuallyDoTheStuff on each iteration:
function actuallyDoTheStuff( iteration ) {
console.log( "Waiting...")
return new Promise( res => {
setTimeout( () => {
res( iteration );
}, 150 );
} );
}
async function doTheStuff() {
for ( let i = 0; i <= 5; i++ ) {
const result = await actuallyDoTheStuff( i );
console.log( result );
}
}
doTheStuff();
Or alternatively make actuallyDoTheStuff a recursive function:
let index = 1;
async function actuallyDoTheStuff( i ) {
if ( i <= 5 ) {
console.log( "Waiting..." )
await new Promise( res => {
setTimeout( () => {
console.log( i );
i++
res();
actuallyDoTheStuff( i );
}, 150 );
} );
}
}
actuallyDoTheStuff( index );

Categories

Resources