Jasmine: how to test async function? - javascript

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

Related

Wait until promise returns true

I have a database setup with NodeJS and want to wait until certain table is created before start to create any others. This method tableExists resolves with the status being either true/false, but I want it to wait until it's true only.
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
return exists;
}
How can I force a wait until checkTableExists returns true?
Using setTimeout:
const CHECK_INTERVAL = 200; // every 200ms
const checkTableExists = async () => {
const exists = await queryInterface.tableExists('Subjects');
if (!exists) {
return new Promise((resolve, reject) => {
setTimeout(() => checkTableExists().then(resolve).catch(reject), CHECK_INTERVAL);
});
}
return exists;
}
The solution to something like this is not to keep on waiting. There are other issues that may cause the table not to be created. You may want to adjust the above code to stop checking after it has checked for set number of times, or a duration has passed. Use something reasonable, depending on the environment where your db is running.
Add a delay and repeat:
// Utility function
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const checkTableExists = async () => {
while (true) {
const exists = await queryInterface.tableExists('Subjects');
if (exists) return true;
await delay(10000); // Wait 10 seconds before trying again.
}
}
Although this resolves the promise with true, it is actually is not necessary to return a boolean, as the resolving promise is enough as a signal that the table now exists -- true is the only possible outcome when the promise resolves.

How does JavaScript call web APIs?

As far as I know, JavaScript's normal behavior when I call a web API just like the setTimeout 4 times:
it should call the first one then add it to a queue waiting for the call stack to be empty .. repeatedly it will do the same for all other apis .. so the second function should wait till the first executes then start to be called .. which means that it should take the second function 2 seconds or more, then it should take the third function three seconds or more ... and so on ...
what am I missing !?
Try to use async await and wait for each actions to be done.
const sleep = (ms) => new Promise(e => setTimeout(e, ms));
const logSeconds = async () => {
await sleep(1000);
console.log(new Date().toLocaleTimeString());
}
const main = async () => {
await logSeconds();
await logSeconds();
await logSeconds();
}
main()
var TimeOutOnTimeOut = () => {
setTimeout(() => {
console.log("Like this!");
TimeOutOnTimeOut();
}, 3000);
};
TimeOutOnTimeOut();
They'll all call at the same time, you could, however, call the next after the first is finished as described.

Pausing/resuming code executing in nodejs

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>

Repeat function itself without setInterval based on its finish

I need my program to repeat itself continuously. My program starts to fetch proxies from servers and saves them to a database and then send those saved proxies to another server again. So I don't know how long it takes for my program to do this task.
I wanna know what happens if any problem happens that makes this startJob() function take more than 30 seconds.
Does setInterval call it again or waits for function to finish?
What's the best approach for my program to repeat itself after it's done without setInterval?
(for exmaple startJob() being called again after it's done.)
I was wondering if it is ok to put this function in a loop with a big number like:
for ( let i = 0 ; i < 999999999 ; i ++ ) {
await startJob()
}
Here is my code:
const startJob = async () => {
await postProxyToChannel()
grabProxies()
}
setInterval(function(){
startJob()
}, (30000))
grabProxies() takes about 10 seconds and postProxyToChannel() takes about 5 seconds on my server.
No matter what happens inside startJob, setInterval will call it every 30 seconds. This means that postProxyToChannel will be called every 30 seconds. If that function throws, you'll get an unhandled Promise rejection, but the interval will continue.
Even if postProxyToChannel takes, say, 45 seconds, that won't prevent startJob from being called again before the prior startJob has completed.
If you want to make sure that startJob is only called 30 seconds after it finishes, you could await it in your for loop, then await a Promise that resolves every 30 seconds:
(async () => {
for ( let i = 0 ; i < 999999999 ; i ++ ) {
await startJob();
await new Promise(resolve => setTimeout(resolve, 30000));
}
})()
.catch((err) => {
console.log('There was an error', err);
});
But it would probably make more sense just to have a recursive call of startJob, eg:
const startJob = async () => {
try {
await postProxyToChannel();
} catch(e) {
// handle error
}
grabProxies();
setTimeout(startJob, 30000);
};
startJob();
Yup an infinite loop sounds good, that can be compared with a timer to pause the loop:
const timer = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function() {
while(true) {
await postProxyToChannel();
await grabProxies();
await timer(30000);
}
})();
Now that loop will run the task, wait 30secs, then do that again. Therefore the loop will not run every 30secs but will usually take longer. To adjust that, you could measure the time the task took, then await the rest of the time:
const start = Date.now();
await postProxyToChannel();
await grabProxies();
await timer(30000 - (Date.now() - start));

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