Pausing/resuming code executing in nodejs - javascript

So i am making some kind of game, where a player has some powerups. After the players turn is over, there should be a 5 sec timeout in the server, where no code is executed, and then the turn should be passed after the time. However if the client clicks in one of the powerups, the server should stop the 5 second timeout and start executing again. How do i implement this?
Currently i am using,
await new Promise(r => setTimeout(r, 5000))
which stops and waits for the timeout to end, but how can I stop the timeout when the client selects a powerUp? How do we clear Promise based timeout?
To be short what i want to do is:
server side code
function doSomething(){
if(playerHasPowerUps) await new Promise(r => setTimeout(r, 5000))
//do other things
}
in client side in this period if the player clicks on a powerup, it
informs the server about the event and the server is meant to stop
the above timeout and do other things

My solution to this will be creating a class that manages Promise and Timeout instances.
Let's name this class Sleep, it takes duration in its constructor and schedule timeout upon the given duration as well as creating a promise instance.
Add an async function wait() which returns the promise instance so that we can await on.
Add a function cancel() which simply resolve the promise instance and clear timeout.
<html>
<body>
<button onClick="cancelWait()">Cancel wait</button>
<div id="text"></div>
</body>
<script lang="javascript">
class Sleep {
constructor(duration) {
this.promise = new Promise((resolve) => {
this.promiseResolve = resolve
this.timeout = setTimeout(() => {
resolve()
}, duration)
})
}
async wait() {
return await this.promise
}
cancel() {
clearTimeout(this.timeout)
this.promiseResolve()
}
}
//Usage
let sleep
const main = async () => {
const text = document.getElementById("text")
text.innerText = 'start'
sleep = new Sleep(3000)
await sleep.wait()
text.innerText = 'finish'
}
const cancelWait = () => {
sleep.cancel()
}
main()
</script>
</html>

Related

JavaScript (React): How to extend async function's await to a given time

I would like to have a async function that sends a request to a server and awaits the response. It will then wait upto 1000ms to ensure that an animation has played out.
i was wondering if I could combine these 2 tasks somehow so they add up to having waited 1000ms in total dynamically.
My current code:
const loadingHandler = async (func, target) => {
setIsLoading(true);
await new Promise(r => setTimeout(r, 1000));
await func(target);
setIsLoading(false);
};
Some examples to further explain what exactly I mean: the function calls func() and gets a response after (lets say) 200ms. Now the timeout is only supposed to be 800ms. If the func() returns after 20ms the timeout is supposed to be 980ms. If func() takes longer than 1000ms it should continue immedietly after getting the response and not wait additionally.
So is something like this 'stretcher await function' possible?
Sure, just remember how long it's been so far, then delay that much longer:
const loadingHandler = async (func, target) => {
setIsLoading(true);
const started = Date.now();
await func(target);
const elapsed = Date.now() - started;
const delayFurther = 1000 - elapsed;
if (delayFurther > 0) {
await new Promise(r => setTimeout(r, delayFurther));
}
setIsLoading(false);
};
That said, holding up the user because an animation hasn't finished might not be the best UX. Perhaps you could make the animation finish more quickly when func is already done. (Humans tend to notice delays > about 80-100ms, a bit less when we're younger.)
An alternative to Mr Crowder's perfectly valid solution: Rather than wait for the promise to finish and then checking if we need to start a new one, start two promises at the same time and wait for them both. Has the same effect, but makes the code shorter:
const loadingHandler = async (func, target) => {
setIsLoading(true);
await Promise.all([
func(target),
new Promise(r => setTimeout(r, 1000)),
]);
setIsLoading(false);
}
In your code you starts first promise (interval), waits for it to finish, then starts second (func) and wait to finish. You should start both promises and wait for it together.
const loadingHandler = async (func, target) => {
setIsLoading(true);
const p1 = new Promise(r => setTimeout(r, 1000));
const p2 = func(target);
await Promise.all([p1,p2]);
setIsLoading(false);
};

How can I do I cancel javascript await sleep?

The most common implementation of a sleep function in javascript is returning a Promise after setTimeout resolves:
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
I have for loop with await sleep to keep it from executing too fast, such as not requesting xhr too fast. I also have a isBreak flag elsewhere to tell me when to stop the for loop. However, the issue I have is that when I break the for loop, the previous await sleep has already executed and is holding up the for loop. Is there a better way of breaking the for loop and also terminating the await sleep instantaneously?
const items = [];
let isBreak = false; // Somewhere else in the application
for (const item of items) {
if (isBreak) break;
// Do something, like xhr request
await sleep(15000); // 15 seconds sleep
if (isBreak) break;
}
Is there a way for me to signal for early
In JS, when an await operation starts, it can no longer be interrupted; it will wait until its operand promise is settled.
So, you have to make the promise you're awaiting cancelable in some way.
Unfortunately, your code can't get notified about a variable reassignment (when you set isBreak to true), and polling it would be inefficient.
Instead of a flag, you could use an AbortSignal (which was invented for this purpose), and make your sleep accept one:
function sleep(ms, signal) {
return new Promise((resolve, reject) => {
signal.throwIfAborted();
const timeout = setTimeout(() => {
resolve();
signal.removeEventListener('abort', abort);
}, ms);
const abort = () => {
clearTimeout(timeout);
reject(signal.reason);
}
signal.addEventListener('abort', abort);
});
}
Then, you use it like this:
const items = [];
const isBreak = new AbortController(); // Somewhere else in the application, call `isBreak.abort()`
try {
for (const item of items) {
// Do something, like xhr request
await sleep(15000, isBreak.signal); // 15 seconds sleep
}
} catch (e) {
if (e.name === 'TimeoutError') {
// Handle a cancellation
console.log('Cancelled');
} else {
// Not a cancellation, rethrow it
throw e;
}
}
An AbortSignal works well with fetch as well, in case you have to cancel that too.
An answer i found in a blog in the past that i adjusted. It is similiar to FZs answer. Same usage, too. Just to give an alternative.
relevant too: How to cancel timeout inside of Javascript Promise?
function sleep(ms, abortSignal) {
return new Promise((resolve, reject) => {
signal.addEventListener("abort", abort);
if(abortSignal.aborted){
abort();
}
const timeout = setTimeout(end, ms);
function abort() {
clearTimeout(timeout);
abortSignal.removeEventListener("abort", abort);
reject(new Error("sleep aborted"));
}
function end() {
abortSignal.removeEventListener("abort", abort);
resolve();
}
});
}

Jasmine: how to test async function?

In my NodeJS app I've got the following heplers.ts file with one method, wait:
export const wait = async (ms: number) => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
I'm currently writing a unit test to this file and this is what I have now:
import { setProcessEnv } from 'spec/helpers';
import { wait } from '../../util/helpers';
describe('Helper methods', () => {
beforeEach(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
setProcessEnv();
});
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
jasmine.clock().install(); // First install the clock
await wait(TIMEOUT);
jasmine.clock().tick(10000); // waits till 10000 milliseconds
expect(wait).toHaveBeenCalled();
jasmine.clock().uninstall(); // uninstall clock when done
});
});
But I'm constantly receiving
Error: Timeout - Async function did not complete within 20000ms (set
by jasmine.DEFAULT_TIMEOUT_INTERVAL)
I've increased jasmine.DEFAULT_TIMEOUT_INTERVAL to 20000 ms, but it still didn't work.
How such async functions could be tested?
I've found coupes examples, but they didn't work in my case: How to test a function which has a setTimeout with jasmine?
UPDATE
This is my latest version that doesn't throw an error, but the problem is that it doesn't cover return statement lines (return new Promise(...):
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
// jasmine.clock().install(); // First install the clock
const wait = jasmine.createSpy();
await wait(TIMEOUT);
// jasmine.clock().tick(1000); // waits till 10000 milliseconds
expect(wait).toHaveBeenCalled();
expect(wait).toHaveBeenCalledWith(TIMEOUT);
// jasmine.clock().uninstall(); // uninstall clock when done
});
The problem is that by using jasmine's custom clock you need to manually call .tick() to move the time forward. Since you immediately await the wait call, the setTimeout within wait will not be reached. Thus, the promise never resolves, as you call .tick() after awaiting the promise.
You can fix this by not immediately awaiting the the promise returned from wait but instead assigning it to a variable and then moving the time ahead. Then, await the promise and check if wait has been called:
describe('Helper methods', () => {
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
jasmine.clock().install(); // First install the clock
const waitPromise = wait(TIMEOUT);
jasmine.clock().tick(10000); // waits till 10000 milliseconds
await waitPromise; // now await the promise
expect(wait).toHaveBeenCalled();
jasmine.clock().uninstall(); // uninstall clock when done
});
});
Note that in the above code no spy has been set up on wait, so .toHaveBeenCalled will probably fail. Check this link if you need help setting up a spy on a function: https://stackoverflow.com/a/43532075/3761628
To answer the updated question: You've incorrectly set up the spy. You need to change it to something like:
import { setProcessEnv } from 'spec/helpers';
import * as WaitFunction from from '../../util/helpers';
...
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
const waitSpy = spyOn(WaitFunction, 'wait').and.callThrough();
await WaitFunction.wait(TIMEOUT);
expect(waitSpy).toHaveBeenCalled();
expect(waitSpy).toHaveBeenCalledWith(TIMEOUT);
});

How can I clear timeout for an asynchronous function in JavaScript

Let me be very clear so as to avoid confusion.
I have a sleep function (below) which timeouts for as many millisecond as I specify.
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
Now in another function beginTest() I called sleep for some millisecond let say 5000 ms.
async function beginTest()
{
await sleep(5000);
}
I have a separate function which gets executed when a button is clicked. I want the function to clear the timeout whenever the button is clicked before completion of 5000 ms. I understand that after 5000 ms clicking the button should not affect anything as the promise has already been resolved.
document.getElementById("reactTimeClick").onmousedown = function()
{
cleartimeOut(); // Clear timeout of above function
}
It's pretty simple to do as you propose with a "cancellation token" via which the "sleep timeout" can be cancelled:
// don't do this
function sleep(ms, cancellationToken) {
return new Promise(resolve => function() {
let timeoutRef = setTimeout(resolve, ms);
cancellationToken.cancel = function() {
clearTimout(timeoutRef);
};
});
}
However that will not, in itself, cause the sleep promise to depart from its pending state and the promise chain (or awaiting statements) stemming from sleep() will not be informed of the cancellation. Progress will hang.
This shortcoming can be overcome, again with a cancellation token, but this time one that allows the sleep() promise to be rejected, as follows ...
// do this
function sleep(ms, cancellationToken) {
return new Promise((resolve, reject) => function() {
cancellationToken.cancel = function() {
reject(new Error('sleep() cancelled'));
};
setTimeout(resolve, ms));
}
}
... and in the caller, use as follows:
async function beginTest() {
try {
const token = {};
const promise = sleep(5000, token);
$('#cancelButton').on('click', token.cancel); // jQuery example, or similar in POJS
await promise;
// ... test code ...
// ... return whatever;
}
catch(error) {
console.log(error.message);
// If button was clicked before the 5000 ms has expired,
// and no other error has been thrown,
// then the log will show "sleep() cancelled".
throw error; // rethrow error to keep beginTest's caller informed.
}
}

While Loop inside ASYNC AWAIT

I have some code that continuously updates a series of objects via network calls looks like this. I was wondering if this is bad practice and if there might be a better way. I cant use Set Interval as the time between MakeAsyncCall replies is variable and can cause a leak if the time to make the call is longer than the delay. I will be using this info to update a UI. Will this cause blocking? What are your thoughts? Let me know if you need more info.
let group = [item1, item2, item3];
// Start Loop
readForever(group, 100);
// Function to Delay X ms
const delay = ms => {
return new Promise((resolve, _) => {
const timeout = setTimeout(() => {
resolve();
}, ms);
});
};
// Function to continuously Make Calls
const readForever = async (group, ms) => {
while(true) {
// Make Async Call
for (let item of group) {
await MakeAsyncCall(item);
}
// Wait X ms Before Processing Continues
await delay(ms);
}
};
The given code won't cause any UI blocking. And is a valid way to update the UI continually.
Instead of a loop you could write it that way:
const readForever = async (group, ms) => {
// Make Async Call
for (let item of group) {
await MakeAsyncCall(item);
}
// Wait X ms Before Processing Continues
await delay(ms);
if (true) { // not needed, but there you could define an end condition
return readForever(group, ms);
}
};
In addition to the comment about the delay function:
You could directly pass the resolve to setTimeout, and because you do not cancel the Timeout anywhere you do not need to store the result setTimeout in a variable.
const delay = ms => {
return new Promise(resolve => setTimeout(resolve, ms));
};

Categories

Resources