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

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 );

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 call clearTimeout and still run the setTimeout's function?

Here is my code:
let delayTimeout = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
After the axios call, I may want to terminate the delayExecution by clearing the timeout inside it. How do I clearTimeout inside my delayExecution function but still resolve the Promise?
In essence, I'm trying to finish delayExecution before its time, but still resolve the promise inside it.
Based on your edit, I'll just leave another response. Note that I haven't tested it, my mind is currently focused on my code I'm writing alongside this hehe
let delayTimeout = null;
let resolveHandler = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
resolveHandler = resolve;
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
resolveHandler('ok');
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
The idea is just to assign the resolve function to another auxiliary variable which you can then use elsewhere :)
doneFunc should have the clearTimeout within it, so after the function is complete the timeout is cleared.
Also, for the first setTimeout parameter, you can just pass the name of the function.
Actually for timeout, you don't need the clearTimeout since it will only be ran ONCE compared to interval which is continuing run.
const doneFunc = () => {console.log('Finished job');clearTimeout(f);}
const f = setTimeout(doneFunc, 100);
If you want to run the function independently from the timeout, just declare the function outside of it, then call it whenever you want. You have most of the code done
const doneFunc = () => console.log('Finished job');
const f = setTimeout(() => doneFunc(), 10000);
/* Seome logic here */
if (condition to run before timeout) {
clearTimeout(f);
doneFunc();
}
/* end of logic */
I have imagined that :
const runOnDelay = function( fct, delay )
{
let obj = {}
, isDone = false
, refTim = setTimeout(()=>
{
isDone = true
fct()
}, delay)
;
obj.stop = () =>
{
clearTimeout(refTim)
if (!isDone)
fct()
isDone = true
}
return obj
}
usage:
const doneFunc = () => console.log('Finished job')
let myBoy = runOnDelay(doneFunc, 1000)
//...
myBoy.stop()

Sequential function calls

I have an array of asynchronous functions, it is necessary to call in order and the result of the call of the previous function is passed into the arguments. How can this be done approximately?
// lets say we have a function that takes a value and adds 20 to it asynchronously
const asyncPlus20 = num => Promise.resolve(num+a)
const arr = [asyncPlus20, asyncPlus20]
let res = 0 // some starting value
for (const f of arr) res = await f(res)
// res is now 20
One of the best way for Array of Async functions is to use For...of.
Run the below snippet in the console. >> Also includes the argument passing
const twoSecondsPromise = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('2000_'), 2000);
})
};
const threeSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '3000_'), 3000);
})
};
const fiveSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '5000_'), 5000);
})
};
(async function () {
const asyncFunctions = [twoSecondsPromise, threeSecondsPromise, fiveSecondsPromise];
let result;
for (const file of asyncFunctions) {
result = await file(result);
console.log(result);
}
})();

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))

How to cancel JavaScript sleep? [duplicate]

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().

Categories

Resources