const function1 = () =>
new Promise(function(resolve,reject) {
setTimeout(() => {
resolve(10)
},6000)
});
const function2 = async () => {
console.log("first");
const val = await function1()
console.log("second");
return val
}
console.log("third -- " ,function2())
I was exepecting the order of the message as below:
first
second
third -- Promise { <pending> }>
But it turns out to give the below output:
first
third -- Promise { <pending> }
second
can anyone please help me understanding this ?
rather than calling function2() you need to await on it
await function2(); // logs first (6 seconds expire) second, 10
this is because you need to wait for function 2 to resolve the Promise before proceeding
you could do the following for the desired result
const function3 = async () => {
const third = await function2();
console.log( "third --", third )
}
function3();
explanation of your code
console.log("first"); - goes first, I hope that there is no questions
when interpretator see this line const val = await function1() - it waits here and continue to execute synchronous code with is console.log("third -- " ,function2()) and you can see third -- Promise { <pending> }
than promise is resolving and execution of async part continues and you finally can see console.log("second");
to achieve expected behavior try this:
const function1 = () =>
new Promise(function(resolve,reject) {
setTimeout(() => {
resolve(10)
},6000)
});
const function2 = async () => {
console.log("first");
const val = function1()
console.log("second");
return val
}
console.log("third -- " , function2())
It's in the logs buddy :)!
The log with the word third is logged second. Because the log with the word 'second' is resolved inside function2 which is called as a normal function. This code basically assumes you're not interested in the (async) result of function two, so it just retuns the pending promise.
Whenever you want the result (and not the pending promise) of an async function you should always await it of chain a then() where you can log the final result, which in this case is 10.
Try next code
const function1 = () =>
new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(10)
}, 6000)
});
const function2 = async () => {
console.log("first");
const val = await function1()
console.log("second");
return val;
}
const start = async () => {
console.log("third -- ", await function2());
};
start();
Related
This question already has answers here:
Timeout in async/await
(3 answers)
Closed 1 year ago.
In my express application, I am making call to 2 APIs. The 2nd API is managed by 3rd party and sometimes can take more than 5 seconds to respond. Hence, I want to just wait for 1 second for the API to respond. If it does not, just proceed with data from 1st API.
Below is the mock-up of the functions being called.
I am thinking to use setTimeout to throw error if the API takes more than 1 second. If the API responds within 1 second then I just cancel the setTimeout and no error is ever thrown.
But there is problem with this approach:
setTimeout errors cannot be catched using try...catch block.
I cannot use axios's timeout option, as I still need to wait for the 2nd API to finish the processing and save the data in the DB. This will ofcourse, can happen later, when the 2nd API call finishes.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
try {
const one = await apiCall1()
const myt = setTimeout(() => {
console.log('Its taking time, skip the 2nd API Call')
isTimeOut = true
throw new Error('Its taking time')
})
const two = await apiCall2(myt)
} catch (error) {
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2(timeOutInstance) {
console.log('start-apiCall')
await cWait(1800)
clearTimeout(timeOutInstance)
if (isTimeOut) saveInDB()
console.log('done-apiCall')
}
async function apiCall1() {
await cWait(5)
}
async function saveInDB(data) {
console.log('saveInDB')
}
test()
please note, this is not the answer as it was when it was accepted
as I misread the question and failed to call saveInDB in a timed out
situation
Promise.race seems perfect for the job
Also, you'd actually use your cWait function, not for mock-up, but to actually do something useful ... win the race :p
const api2delay = 800;
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const TIMEDOUT = Symbol('TIMEDOUT');
async function cReject(ms) {
return new Promise((_, reject) => setTimeout(reject, ms, TIMEDOUT));
}
function apiCall2timeout(timeoutCallback) {
const resultApi2 = apiCall2();
const timeout = cReject(1000);
return Promise.race([resultApi2, timeout])
.catch(e => {
if (e === TIMEDOUT) {
resultApi2.then(timeoutCallback);
} else {
throw e;
}
});
}
async function test() {
console.log('starting')
let one, two;
try {
one = await apiCall1();
two = await apiCall2timeout(saveInDB);
} catch (error) {
console.log('error', error)
}
saveInDB({
...one,
...two
})
}
async function apiCall2() {
console.log('start-apiCall2')
await cWait(api2delay)
console.log('done-apiCall2')
return {
api2: 'done'
}
}
async function apiCall1() {
await cWait(5)
return {
api1: 'done'
}
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
Note: I changed where one and two were declared since const is block scoped
I you run with await cWait(800) in apiCall2, the saveInDB will run with both data.
But if you run await cWait(1800), the saveInDB will run 2 times.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
const one = await apiCall1() // get data from 1st API
let two = {};
try {
two = await promiseTimeout(1000, apiCall2())
} catch (error) {
isTimeOut = true;
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2() {
console.log('start-apiCall')
await cWait(800)
console.log('done-apiCall', isTimeOut)
if (isTimeOut) {
saveInDB({ 2: 'two' })
}
return { 2: 'two' }
}
async function apiCall1() {
await cWait(5)
return { 1: 'one' }
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
I've found that attempting to test timers with Jest is hard enough without getting async/await involved.
I have a setInterval that's wrapped in a promise. The callback of the setInterval only resolves if the condition is matched for it to resolve (shown below). If the promise is not resolved, it keeps attempting the iteration until it is.
index.js
module.exports = async function() {
let interval
const _promise = new Promise((resolve, reject) => {
interval = setInterval(async () => {
try {
const result = await asyncTask()
if (result && result.val === 'Foo Bar') {
clearInterval(interval)
resolve(result)
}
} catch(err) {
reject(err)
}
}, 10000) // 10 secs
})
const result = await _promise
if (result) {
return result
}
}
I've mocked the return of asyncTask with something like:
__mocks__/asyncTask.js
module.exports = jest.fn()
.mockReturnValueOnce()
.mockReturnValueOnce()
.mockReturnValueOnce({
val: 'Hello World'
})
.mockReturnValue({
val: 'Foo Bar'
})
What I am attempting to test is the callback of each iteration of the setInterval & I want to try to control that all operations in the callback are finished before I advance the timer again & the next setInterval happens. For example, with the mock above, a truthy result will be returned on the 3rd invocation of that function. The 4th invocation would also be a truthy result & would match the condition for the interval to be cleared & the promise to be resolved. 1 test I would look to do, for example, is have the setInterval iterate 3 times & even though asyncTask returns an object, the promise is not yet resolved. A few examples of how I've attempted this are below:
index.test.js
jest.useFakeTimers()
const _test = require('./index.js')
describe('test()', () => {
test("Should call 'asyncTask' thrice (1)", () => {
const promise1 = new Promise((resolve, reject) => {
// I cannot await `_test()` here as the fast-forward of the timer would never happen
// So I would actually have to wait 10 seconds
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
const promise2 = new Promise((resolve, reject) => {
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
const promise3 = new Promise((resolve, reject) => {
_test().then(resolve)
jest.advanceTimersByTime(10000)
})
// I have to do it like this over a `Promise.all()` I imagine as I can't risk having them run in parallel
// I imagine that has the potential to mess up the mock function call count of `asyncTask` so would not be reliable
const result1 = await promise1
const result2 = await promise2
const result3 = await promise3
expect(result1).toBeUndefined()
expect(result2).toBeUndefined()
expect(result3).toBeUndefined()
})
test('Should call `asyncTask` thrice (2)', async () => {
_test().then(res => {
expect(res).toBeInstanceOf(Object)
})
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
await jest.advanceTimersByTime(10000)
})
})
The latter was probably closer to what I want to achieve as in the former example; promise1, promise2, & promise3 will never resolve so the test will never complete.
I can test this successfully is I use real timers (jest.useRealTimers()) instead of fake ones but the obvious issue is I would have to wait 40 secs for the promise to resolve & the test to complete.
Is there any clean way to achieve what I want here? Any help appreciated :)
I have to test response of endpoints using Mocha and chai tests. Below is the code for the same :
async function getData (userId) {
let response;
let interval = setInterval(async () => {
response = await superagent.get("localhost:3000/user/details/").query({'user': userId}).type('application/json');
if (response.body["status"] == 'DONE') {
clearInterval(interval);
response = await superagent.get("localhost:3000/user/details/get").type('application/json');
}
}, 10000);
return response;
}
Test Code :
it('User Get Data', async function () {
return getData(userId,).then(function (res) {
expect(res).to.exist;
expect(res.status).to.equal(200);
expect(res.body).to.contain('operation');
expect(res.body["userDetails"]).to.exist;
});
I always get the response as null and my test fails . Kindly let me know where am I going wrong with this code.
Don't use setInterval with promises, and never pass an async function as a callback when the returned promise is ignored. In your case, use a loop instead:
async function getData (userId) {
let response;
do {
await delay(10000);
response = await superagent.get("localhost:3000/user/details/").query({'user': userId}).type('application/json');
} while(response.body["status"] != 'DONE');
return superagent.get("localhost:3000/user/details/get").type('application/json');
}
with
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Edited to get rid of the while loop:
You can rewrite getData with async/await and wrapping your interval in a promise. Once the first response is ok, clear the interval, resolve the promise and execute the second call.
In your unit-test simply await this function and then verify the response details. Note that you might want to increase the default mocha-timeout for the test, as this could potentially take a while. Something like:
async function getData(userId) {
const firstResponsePromise = new Promise(resolve => {
const interval = setInterval(async() => {
const response = await superagent.get('localhost:3000/user/details/').query({
'user': userId
}).type('application/json');
if (response.body['status'] == 'DONE') {
clearInterval(interval);
resolve();
}
}, 10000)
});
await firstResponsePromise;
return superagent.get('localhost:3000/user/details/get').type('application/json');
}
// unit test
it('User Get Data', async function () {
const res = await getData(userId);
expect(res).to.exist;
expect(res.status).to.equal(200);
expect(res.body).to.contain('operation');
expect(res.body["userDetails"]).to.exist;
});
I have a simple yet perplexing issue with async functions.
I wish to simply return the value when its ready from the function.
Here is a sample code:
async function test() {
setTimeout(function() {
return 'eeeee';
}, 5000);
}
test().then(x => {
console.log(x)
});
You will get undefined been logged at once.
It's clear that you are trying to write a sleep() async function, but do remember that setTimeout is a sync function calling with a callback function will be executed at a given time, so while you are executing test(), the calling will run to end and return undefined as you have no return statement in the function body, which will be passed to your .then() function.
The right way to do this is to return a Promise that will be resolved after a given time, that will continue the then call.
async function sleep(time){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve("echo str")
},time)
})
}
sleep(5000).then((echo) => console.log(echo))
sleep function in short
const sleep = async time => new Promise(resolve=>setTimout(resolve,time))
With Promises
const setTimer = (duration) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Done!');
}, duration);
});
return promise;
};
setTimer(2000).then((res) => console.log(res));
An async function has to return a promise. So to fix this, you can wrap your setTimeout function in a new promise like so:
async function test(){
return await new Promise((resolve, reject) => {
setTimeout(function(){
resolve('eeeee');
},5000);
})
}
test().then(x => {
console.log(x)
});
You can learn more about async/await on the MDN docs here. Hope this helps!
Consider testing the following simplified function
const functionToBeTested = async (val) => {
await otherModule.otherFunction(val/2);
}
In my jest test I want to make sure that the otherModule.otherFunction is not only called but also waited on. In other words, I want to write a test that will fail if someone removes the await from in front of the otherFunction call.
I have so far this
test('should wait on otherFunction', () => {
await functionToBeTested(6)
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
}
But the expect(otherModule.otherFunction).toHaveBeenCalledWith(3); check does not verify that functionToBeTested has waited on otherFunction.
Here's what i came up with:
const delay = duration => new Promise(resolve => setTimeout(resolve, duration));
test('should wait on otherFunction', async () => {
let resolve;
const mockPromise = new Promise((res) => {resolve = res;});
otherModule.otherFunction.mockReturnValue(mockPromise);
const resolution = jest.fn();
functionToBeTested(6).then(resolution);
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
await delay(0);
expect(resolution).not.toHaveBeenCalled();
resolve();
await delay(0);
expect(resolution).toHaveBeenCalled();
}
So, i mock otherFunction to return a promise which starts unresolved, but i can resolve it at will during the test. Then i call the function i want to test, and give it a callback for when its complete.
I then want to assert that it did not call the callback, but since promise resolution is always asynchronous i need to add in a timeout 0 to give the promise a chance to resolve. I chose to do this with a promis-ified version of setTimeout.
And finally, i resolve the mockPromise, do a timeout 0 (again, to make sure the promise gets a chance to call its callbacks), and assert that now the resolution has been called.
If you cannot check against otherModule.otherFunction resolved value or on any side-effects, there is no need to test wether it resolves.
Otherwise, removing await in following examples will cause the tests to fail.
describe('check for side effect', () => {
let sideEffect = false;
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
sideEffect = true;
resolve();
}, 0);
}),
};
const functionToBeTested = async val => {
await otherModule.otherFunction(val / 2);
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
await expect(functionToBeTested(6)).resolves.toBeUndefined();
expect(spy).toHaveBeenCalledWith(3);
expect(sideEffect).toBe(true);
});
});
describe('check returned value', () => {
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 0);
}),
};
const functionToBeTested = async val => {
const res = await otherModule.otherFunction(val / 2);
return `*** ${res} ***`;
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
const promise = functionToBeTested(6);
expect(spy).toHaveBeenCalledWith(3);
await expect(promise).resolves.toBe('*** hello ***');
});
});