Here's my code which I use to delay process (for backoff)
export function promiseDelay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
I would want to test it but I'm not able to. I tried working with fakeTimers but my test never ends.
test('promiseDelay delays for 1s', async (done) => {
jest.useFakeTimers();
Promise.resolve().then(() => jest.advanceTimersByTime(100));
await promiseDelay(100);
});
promiseDelay returns a Promise that resolves after ms so call a spy in then and test to see if the spy has been called after different intervals:
describe('promiseDelay', () => {
beforeEach(() => { jest.useFakeTimers(); });
afterEach(() => { jest.useRealTimers(); });
test('should not resolve until timeout has elapsed', async () => {
const spy = jest.fn();
promiseDelay(100).then(spy); // <= resolve after 100ms
jest.advanceTimersByTime(20); // <= advance less than 100ms
await Promise.resolve(); // let any pending callbacks in PromiseJobs run
expect(spy).not.toHaveBeenCalled(); // SUCCESS
jest.advanceTimersByTime(80); // <= advance the rest of the time
await Promise.resolve(); // let any pending callbacks in PromiseJobs run
expect(spy).toHaveBeenCalled(); // SUCCESS
});
});
Note that test code is synchronous and Timer Mocks make setTimeout synchronous but then queues a callback in PromiseJobs so any queued callbacks need to be allowed to run before testing if the spy has been called.
This can be done by using an async test function and calling await on a resolved Promise which effectively queues the rest of the test at the end of PromiseJobs allowing any pending callbacks to run before the test continues.
Additional information about how promises and fake timers interact is available in my answer here.
I think you just need to return the promise from the function like
test('promiseDelay delays for 1s',() => {
jest.useFakeTimers();
return Promise.resolve().then(() => jest.advanceTimersByTime(100));
});
and then spy the setTimeout to be called once.
Related
I have a very simple code snippet like this
async function neverResolve() {
return new Promise(() => {
console.log("This promise will never resolve");
});
}
(async () => {
try {
console.log("START");
// neverResolve().then().catch(); // uncommenting this line works as expected
await neverResolve();
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
console.log("END");
} catch (error) {
console.log("ERR: ", error);
}
})();
Why the above function doesn't wait for 5 second and print's the END.
It automatically terminates after printing
START
This promise will never resolve
But if we execute the same function but with a .then() construct, I get the expected result.
async function neverResolve() {
return new Promise(() => {
console.log("This promise will never resolve");
});
}
(async () => {
try {
console.log("START");
neverResolve().then().catch();
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
console.log("END");
} catch (error) {
console.log("ERR: ", error);
}
})();
This is what's happening in your case:
Your program starts it's execution from an anonymous IIFE async function, as this is an async function, it immediately returns a Promise to a global scope.So the execution of anonymous IIFE is deferred .
You can easily validate this by adding a console.log("OK"); at the end of your IIFE invocation, which is printed to the console
Node keeps a reference count of things like timers and network requests. When you make a network, or other async request, set a timer, etc. Node adds on to this ref count. When the times/request resolve Node subtracts from the count. Ref. video link
So what happens inside your IIFE is:
console.log("START"); <--- gets printed to console
await neverResolve(); Here things get's interesting, this await call will defer the execution and blocks until the callback are executed, either resolve or reject.
But in this case there are no callbacks registered and nodejs will think that it finished processing all the request and will terminate the process.
neverResolve neither resolves or rejects, so the program hangs indefinitely at the await. Consider abstracting the timeout functionality in its own generic function, timeout -
const sleep = ms =>
new Promise(r => setTimeout(r, ms));
const timeout = (p, ms) =>
Promise.race([
p,
sleep(ms).then(() => { throw Error("timeout") })
]);
const neverResolve = () => new Promise(() => {});
(async function() {
try {
console.log("connecting...");
await timeout(neverResolve(), 2000);
}
catch (err) {
console.error(err);
}
})();
In the first example await neverResolve(); waits forever and never resolves as stated. Javascript can go off and do other things in other tasks while waiting for this.
In the second example by adding .then() you've told javascript to continue processing code below. Typically, all the code below would either be inside the callback for then() or catch() which would then create the same pause you're seeing.
There are very deliberate reasons for these nuances that allow you to send a fetch request, do other work and then come back to see if the fetch is done later. See my comments in this marked up and slightly modified example.
async function slowFetch() {
return new Promise((resolve) => {
// a fetch statement that takes forever
fetch().then(data => resolve(data));
});
}
(async () => {
try {
console.log("START");
// start the slow fetch right away
const dataPromise = slowFetch();
// do some other tasks that don't take as long
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
// wait for the data to arrive
const data = await dataPromise;
// do something with the data like fill in the page.
console.log("END");
} catch (error) {
console.log("ERR: ", error);
}
})();
By doing neverResolve().then().catch(); what you actually did is;
async function neverResolve() {
return new Promise(() => {
console.log("This promise will never resolve");
});
}
(async () => {
try {
console.log("START");
(async function(){
try {
await neverResolve()
}
catch{
}
})();
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
console.log("END");
} catch (error) {
console.log("ERR: ", error);
}
})();
The inner async IIFE runs just like neverResolve().then().catch(); does. You should not mix promises with async await abstraction.
Why the above function doesn't wait for 5 second and print's the END. It automatically terminates after printing
Because your code never gets past this:
await neverResolve();
Since neverResolve() returns a promise that never resolves or rejects, this function is forever suspended at that line and the lines of code after this statement in the function never execute.
await means that the function execution should be suspended indefinitely until the promise you are awaiting either resolves or rejects.
But if we execute the same function but with a .then() construct, I get the expected result.
When you change the await to this:
neverResolve().then().catch();
The function execute is NOT suspended at all. It executes neverResolve(). That returns a promise which it then calls .then() on. That call to .then() just registers a callback with the promise (to be called later when the promise resolves). That returns another promise which it then calls .catch() on which just registers a callback with the promise (to be called later if/when the promise rejects).
Now, you aren't even passing a callback in either case, so those .then() and .catch() have nothing to actually do, but even if you did pass a callback to each of them, then they would just register that callback and immediately return. .then() and .catch() are not blocking. They just register a callback and immediately return. So, after they return, then next lines of code in the function will execute and you will get the output you were expecting.
Summary
await suspends execution of the function until the promise you are awaiting resolves or rejects.
.then() and .catch() just register callbacks for some future promise state change. They do not block. They do not suspend execution of the function. They register a callback and immediately return.
I have encountered a problem when writing a unit test around an async function using setTimeout. Here's the simplified version:
var counter = 0;
const sut = async () => {
await Promise.resolve("Task A");
await Promise.resolve("Task B");
counter++;
setTimeout(() => sut(), 100);
}
// unit test with Jest:
it('Test', async () => {
jest.useFakeTimers();
await sut();
jest.advanceTimersToNextTimer();
await Promise.resolve();
await Promise.resolve(); // without this repeated await, the test fails!!
expect(counter).toBe(2);
}
Suprisingly, in my unit test, I have to await on a resolved Promise two times i.e. equal to the number of times I await in sut.
I cannot explain this. My assumption was it would be enough to make sure micro-task queue is used up in order to advance testing flow.
Can someone shed a light here please?
I (first time JavaScript user since yesterday) managed to get JavaScript to run functions in sequential execution order (see code below) (credit to #CertainPerformance). I need to use the fastFunction in multiple slowFunctions. The current solution does not seem DRY (do not repeat yourself) to me and at the same time it does not guarantee the exectution order of slowFunction1 and then slowFunction2. What is the DRY solution to this problem in JavaScript? Can I force JavaScript to always run in sequential mode by some configuration? Using nested callbacks does not seem to be the most intelligent solution to me.
function fastFunction(message) {
console.log(message);
}
function slowFunction1(callback, message) {
setTimeout(() => {
console.log('slowFunction1!');
callback(message);
}, 10000);
}
function slowFunction2(callback, message) {
setTimeout(() => {
console.log('slowFunction2!');
callback(message);
}, 1000);
}
slowFunction1(fastFunction, 'fast_Function');
slowFunction2(fastFunction, 'fast_Function');
With async/await you can sequence asynchronous tasks as follows:
// Very handy utility function to get a promise that resolves after a given delay
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function fastFunction(message) {
console.log(message);
}
async function slowFunction1(callback, message) {
console.log('slowFunction1!');
await delay(2000); // two seconds
callback(message);
}
async function slowFunction2(callback, message) {
console.log('slowFunction2!');
await delay(1000); // one second
callback(message);
}
(async function() {
// Put all your logic here, and await promises...
await slowFunction1(fastFunction, 'fast_Function');
await slowFunction2(fastFunction, 'fast_Function');
})(); // execute immediately
Now you will have the delays happening one after the other completes, so 2+1=3 seconds in (approximate) total execution time.
This mimics most what you had as pattern, but once you are using promises, you don't need the callback pattern anymore and can do it like this:
// Very handy utility function to get a promise that resolves after a given delay
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function fastFunction(message) {
console.log(message);
}
(async function() {
console.log('slow part 1');
await delay(2000); // two seconds
fastFunction('fast_function');
console.log('slow part 2');
await delay(1000); // one second
fastFunction('fast_function');
})(); // execute immediately
Trying to do a relatively simple assertion with jest. I have the following test setup:
const sleep = ms => new Promise(res => setTimeout(res, ms));
it('should call callback after specified delay', async () => {
const mockCallback = jest.fn();
setTimeout(1000, mockCallback);
expect(mockCallback).not.toHaveBeenCalled();
await sleep(1000);
expect(mockCallback).toHaveBeenCalled();
});
When I run the test fails with the following error:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Obviously this is nowhere near that threshold. Any idea what I'm doing wrong?
[UPDATE]
I realized previously I had called jest.useFakeTimers() before the test. After removing this and running the test again I still get a failure but it's not a timeout. Instead just simply
Expected mock function to have been called, but it was not called.
Note this is also the case when significantly increasing the sleep to 4000ms.
If instead I switch from setTimeout to
sleep(ONE_SECOND)
.then(mockCallback);
the test passes. Jest clearly modifies how setTimeout interacts in parallel with Promises, but it's not obvious as to what is going on.
You just need to pass mockCallback as the first argument to setTimeout:
const sleep = ms => new Promise(res => setTimeout(res, ms));
it('should call callback after specified delay', async () => {
const mockCallback = jest.fn();
setTimeout(mockCallback, 1000); // <= pass mockCallback as first argument
expect(mockCallback).not.toHaveBeenCalled(); // Success!
await sleep(1000);
expect(mockCallback).toHaveBeenCalled(); // Success!
});
If I have something like this setup:
<-- language: lang-javascript -->
console.clear();
// noprotect
const fetchSomething = () => new Promise((resolve) => {
setTimeout(() => resolve('future value'), 500);
});
async function asyncFunction() {
const result = await fetchSomething();
console.log('waiting');
setTimeout(()=>console.log('waiting?'), 250);
return result + ' 2';
}
asyncFunction().then(result => console.log(result));
And my output looks like:
"waiting"
"future value 2"
"waiting?"
I would expect the waiting? to execute before the result completes, but for some reason it waits on the function. What makes one wait but the other execute?
It is a feature of Asynchronous programming.
You have to add await and wrap your setTimeout(()=>console.log('waiting?'), 250); with a function which returns Promise in order to make it look like it was evaluated continuously.
Something like:
await ((ms) =>{
console.log('waiting?');
return new Promise(resolve => setTimeout(resolve, ms));
})(250);
Or:
await (() => new Promise(resolve => setTimeout(()=>{
console.log('waiting?');
resolve();
}, 250)))();
Mind you, JS has a single threaded run-time engine, so it interrupts evaluation when original script reaches it's end.
And function in setTimeout(function, timeout) is evaluated by JS when it has a first chance and time is right.
So your function was interrupted twice and was resumed twice.
The call to log "waiting?" is started by a setTimeout after the await has finished, so after the 500ms in fetchSomething have already passed. It will only execute 250ms after fetchSomething has returned. That is enough time for asyncFunction to return.
If you want to see a different behaviour, start the timer for logging "waiting?" before calling await:
async function asyncFunction() {
setTimeout(()=>console.log('waiting?'), 250);
const result = await fetchSomething();
console.log('waiting');
return result + ' 2';
}