How to call clearTimeout and still run the setTimeout's function? - javascript

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

Related

Repeat async function until true

I have an async function that checks for the status of an order (checkOrderStatus()). I would like to repeat this function until it returns either "FILLED" or "CANCELED", then use this return value in another function to decide to continue or stop the code. Every order goes through different status before being "FILLED" or "CANCELED", therefore the need to repeat the checkOrderStatus() function (it is an API call).
What I have now is this, to repeat the checkOrderStatus() function:
const watch = filter => {
return new Promise(callback => {
const interval = setInterval(async () => {
if (!(await filter())) return;
clearInterval(interval);
callback();
}, 1000);
});
};
const watchFill = (asset, orderId) => {
return watch(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
});
};
I then call watchFill() from another function, where I would like to check its return value (true or false) and continue the code if true or stop it if false:
const sellOrder = async (asset, orderId) => {
try {
const orderIsFilled = await watchFill(asset, orderId);
if (orderIsFilled) {
//… Continue the code (status === 'FILLED'), calling other async functions …
}
else {
//… Stop the code
return false;
}
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
However, this does not work. I can see the status being updated in the terminal via the console.log in watchFill(), but it never stops and most importantly, the value in the orderIsFilled variable in sellOrder() does not get updated, whatever the value returned by watchFill() becomes.
How can I achieve the desired behavior?
watch never calls resolve (in the original code, this is misleadingly named callback()) with any value, so there's no way const orderIsFilled = await watchFill(asset, orderId); will populate orderIsFilled with anything but undefined.
If you save the result of await filter() in a variable and pass it to
callback as callback(result), your code seems like it should work.
That said, the code can be simplified by using a loop and writing a simple wait function. This way, you can return a value (more natural than figuring out how/when to call resolve), keep the new Promise pattern away from the logic and avoid dealing with setInterval and the bookkeeping that goes with that.
const wait = ms =>
new Promise(resolve => setTimeout(resolve, ms))
;
const watch = async (predicate, ms) => {
for (;; await wait(ms)) {
const result = await predicate();
if (result) {
return result;
}
}
};
/* mock the API for demonstration purposes */
const checkOrderStatus = (() => {
let calls = 0;
return async () => ({
status: ++calls === 3 ? "FILLED" : false
});
})();
const watchFill = (asset, orderId) =>
watch(async () => {
const {status} = await checkOrderStatus();
console.log(`Order status: ${status}`);
return status === "CANCELLED" ? false : status === "FILLED";
}, 1000)
;
const sellOrder = async () => {
try {
const orderIsFilled = await watchFill();
console.log("orderIsFilled:", orderIsFilled);
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
sellOrder();
You can use recursive functionality like this:
const checkOrderStatus = async () => {
// ... function does some work ...
await someOtherFunction() // you can use here the other async function as well
// ... function does some more work after returning from await ...
if(/* if status is FILLED or CANCELED */) {
// return true or false or some info about response for your needs
} else {
checkOrderStatus();
}
}
// this will response back when status will be FILLED or CANCELED
await checkOrderStatus();
The watch function clears the interval timer after the first call if filter resolves with false. setInterval doesn't wait for an async function to finish executing either so you'll have to create a loop yourself. Try this:
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
const watch = async check => {
while (true) {
if (await check()) {
return;
}
await delay(1000);
}
};
Because watch only resolves when check succeeds, it is not possible to fail so you don't need to check for it (this might be a bug in your code):
const sellOrder = async (asset, orderId) => {
try {
await watchFill(asset, orderId);
//… Continue the code (status === 'FILLED'), calling other async functions …
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
p-wait-for contains an excellent implementation of this. You can use it like so:
import pWaitFor from 'p-wait-for';
const watchFill = (asset, orderId) => pWaitFor(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
}, {
interval: 1000,
leadingCheck: false
});

jest.advanceTimersByTime doesn't work when I try to test my retry util function

I have a retry util function I wanted to test for. It looks like this
export const sleep = (t: number) => new Promise((r) => setTimeout(r, t));
type RetryFn = (
fn: Function,
config: {
retryIntervel: number;
retryTimeout: number;
predicate: Function;
onRetrySuccess?: Function;
onRetryFail?: Function;
}
) => Promise<any>;
export const retry: RetryFn = async (
fn,
{ predicate, onRetrySuccess, onRetryFail, retryIntervel, retryTimeout }
) => {
const startTime = Date.now();
let retryCount = 0;
while (Date.now() - startTime < retryTimeout) {
try {
const ret = await fn();
if (predicate(ret)) {
if (retryCount > 0) onRetrySuccess && onRetrySuccess();
return ret;
} else {
throw new Error();
}
} catch {
retryCount++;
}
await sleep(retryIntervel);
}
if (onRetryFail) onRetryFail();
};
what it does is retry the function for a period of time at a given interval.
I thought I could use jest.advanceTimersByTime to advance the timer to test how many times the retry happens.
import { retry } from "./index";
const value = Symbol("test");
function mockFnFactory(numFailure: number, fn: Function) {
let numCalls = 0;
return function () {
fn();
numCalls++;
if (numCalls <= numFailure) {
console.log("numCalls <= numFailure");
return Promise.resolve({ payload: null });
} else {
console.log("numCalls => numFailure");
return Promise.resolve({
payload: value
});
}
};
}
describe("retry function", () => {
beforeEach(() => {
jest.useFakeTimers();
});
it("retrys function on 1st attempt, and succeed thereafter", async () => {
const fn = jest.fn();
const onRetrySuccessFn = jest.fn();
const mockFn = mockFnFactory(3, fn);
retry(mockFn, {
predicate: (res: any) => res.payload === value,
onRetrySuccess: onRetrySuccessFn,
retryIntervel: 1000,
retryTimeout: 5 * 60 * 1000
});
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(1);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(2); // 🚨 fail
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(2000);
expect(fn).toHaveBeenCalledTimes(3);// 🚨 fail
expect(onRetrySuccessFn).toHaveBeenCalledTimes(1);
});
});
but it seems like no matter how much I advanced the timer, the function only gets invoked once.
You can find the code on codesandbox at https://codesandbox.io/s/lucid-knuth-e810e?file=/src/index.test.ts
However, there is a known issue with codesandbox where it keeps throwing this error TypeError: jest.advanceTimersByTime is not a function . This error doesn't appear locally.
It's because of this.
Here's what I use in a test helpers file:
const tick = () => new Promise(res => setImmediate(res));
export const advanceTimersByTime = async time => jest.advanceTimersByTime(time) && (await tick());
export const runOnlyPendingTimers = async () => jest.runOnlyPendingTimers() && (await tick());
export const runAllTimers = async () => jest.runAllTimers() && (await tick());
In my test file, I import my helpers and instead of calling jest.advanceTimersByTime, I await my advanceTimersByTime function.
In your specific example, you just need to await a function after calling advanceTimersByTime - like this:
// top of your test file
const tick = () => new Promise(res => setImmediate(res));
... the rest of your existing test file
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(1);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
await tick(); // this line
expect(fn).toHaveBeenCalledTimes(2);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(2000);
await tick(); // this line
expect(fn).toHaveBeenCalledTimes(3)
expect(onRetrySuccessFn).toHaveBeenCalledTimes(1);
I am a little late but I had to solve this problem today and I solved it by making this new util function.
// So we can wait setTimeout loops
export const advanceTimersByNTimes = (n = 1, time = 1000) => {
for (let i = 0; i < n; i++) {
act(() => {
jest.advanceTimersByTime(time * 1);
});
}
};
and this is how I use this in the test:
await waitFor(() => {
expect(screen.queryByTestId("timeout-exceeded-container")).toBeNull();
});
advanceTimersByNTimes(11);
await waitFor(() => {
expect(screen.queryByTestId("timeout-exceeded-container")).not.toBeNull();
});
Nothing else worked for me, including the answers here.
This is the part in my code that doesn't work without the above hack (for reference):
setTimeout(() => setSeconds((prev) => prev + 1), 1000);
It would step to 2 times no matter what I've set jest.advanceTimersByTime to, those calls need to be wrapped in act blocks to count.

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

Keep calling an API every 2.5 seconds and close the call once desired result is achieved

I have an API to call every 2.5 seconds. Initially, the data inside the response object is null as the database is still updating it through a transaction. But on the subsequent 3rd or 4th try, I get the data. I am writing a reusable function for the same, however I get undefined. My goal is to keep calling the API until I get the value in my path and close the connection. Please advice.
P.S: The below API URL doesnt have any delay, but my private API has.
const getData = (url, path) => {
const interval = setInterval(async () => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
return result[path]
}
}, 2500)
return clearInterval(interval)
}
getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion')
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
JS Fiddle URL
Please advice.
You
return clearInterval(interval); // undefined
If you want to return a Promise which will resolve when the data is available, you could do something like this:
const getData = (url, path) => {
return new Promise((resolve, reject) => {
const interval = setInterval(async() => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
clearInterval(interval); // Clear the interval
resolve(result.data[path]); // Resolve with the data
}
}, 2500);
});
}
getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion')
.then(data => {
console.log(data); // Your data is available here
});
// OR
(async () => {
const version = await getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion');
console.log(version);
})();
Its because javascript is asynchronous as above comment is already mentioned. You can use either callbacks or promise in javascript. Here is the code:
const getData = (url, path, cb) => {
const interval = setInterval(async () => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
clearInterval(interval); //We found it remove the interval
cb(result.data[path]);
}
}, 2500);
};
getData(
"https://api.oceandrivers.com/static/resources.json",
"swaggerVersion",
data => {
console.log("test",data);
}
);
Here is the fiddle: https://jsfiddle.net/7pc4hq6t/3/
You could create an asynchronous delay:
const delay = milliseconds => new Promise(resolve, setTimeout(resolve, milliseconds));
Then use like this:
const getDataAsync = async (url, path) => {
while (true) {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
return result.data[path];
}
await delay(2500);
}
}
const data = await getDataAsync('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion');
This avoids the multiple layers of nested callbacks, and produces much more readable code.

"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