I have given up on what I feel is simple. I have the following promise:
information.js
// other methods
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
info['code'] = response[0][1]
})
.catch((e) => {
console.log(e);
});
return info;
}
process.js:
import { information } from "../information"
Promise.all([information()]).then(function (values) {
if (values[0]['code'] != null) {
// tag the code
}
}).catch(err=>{
console.log(err)
});
now in processor.test.js
import * as info from '../information';
it("should tag service code", async () =>{
const spy = jest.spyOn(info,"information")
spy.mockResolvedValue({'code':'ABC'})
expect(tagCode()).toEqual('ABC_Y')
});
it fails saying expected 'ABC_Y' but got null. From console.log on the resolved Promise, I can see it is executing the original information method, instead of my spy thus returning null always.
Please correct me if I'm on the wrong track, however could this be solved by changing your test case slightly?
jest.spyOn(info, 'information').mockImplementationOnce(jest.fn(async () => { code: 'ABC' });
expect(tagCode()).toEqual('ABC_Y');
I haven't tested this code, just my opinion at 4:42am.
I opt to include a jest.fn call within my mockImplementation call, this allows me to test for other things such as information() being called:
expect(info.information).toHaveBeenCalledTimes(1);
expect(info.information).toHaveBeenCalledWith({ code: 'ABC' })
Hope this goes at-least some way toward helping you resolve your issue, although I'll admit I've had many an issue with Jest (especially with dependencies, although these are usually my mistake through context issues).
I've read your question a few times and I'm still not convinced I truly understand it, so please accept my apologies if above is useless to you.
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
// this may produce different results
info['code'] = response[0][1]
})
.catch((e) => {
console.log(e);
});
return info;
}
An issue could be above, you're returning info, I'm assuming with the understanding it could contain the resolved value, from your .then statement, as far as I'm aware this wouldn't work in reality.
The .then is processed at the end of the method (after return), so your info could contain an empty code, then some time after would complete the Promise.
I'd change that from above, to:
export async function information() {
let info= {
code: ''
};
await AsyncStorage.multiGet([
'code'
])
.then((response) => {
info['code'] = response[0][1]
Promise.resolve(info);
})
.catch((e) => {
console.log(e);
});
}
Although I'd recommend not mixing async/await with Promises, as it's a pretty good way to shoot yourself in the foot (my opinion of course).
You can test this theory of course by inserting a comment above your return and inside your .then, a simple console.log('called') | console.log('called1') will give you an indication of which was called first.
Related
Pretty sure I could do this with async/await just fine, but I'd like to understand how to implement this logic without.
Workflow:
Find a job in the database
if the job exists, find a Person, if not, send a response to the frontend
then do Person logic
Code:
Job.findByPk(jobId)
.then(job => {
if(job) return Person.findOne(...)
else res.status(404).json(...)
})
.then(person => {
if(person)
// Do person logic here
})
.catch(err => ...);
The problem is that this logic obviously doesn't work. The person parameter in the second .then() block could be undefined if either no job or no person is found.
So a solution would be to do this in the first .then() block:
.then(job => {
if(job) // store the job locally
else res.status(404).json(...)
return Person.findOne(...)
})
but that means that the database is searched regardless of if a Job is found or not rather than being conditional on a job being found
How do structure this in a way that makes more sense?
Thanks
This is a lot simpler with await (assuming you make the parent function async):
try {
const job = await Job.findByPk(jobId);
if (!job) {
return res.status(404).json(...)
}
const person = await Person.findOne(...);
if (person) {
...
} else {
...
}
} catch(e) {
console.log(e);
res.sendStatus(500);
}
What makes this flow so much simpler is that all variables are in the same scope and you can return anywhere you want to finish the flow.
If you're going to stick with the previous .then() logic, then see this answer: How to chain and share prior results with promises for multiple different options.
You can simply add .thens within the first .then.
Job.findByPk(jobId)
.then(job => {
if(job)
return Person.findOne(...)
.then(person => {
if(person)
// Do person logic here
});
else res.status(404).json(...)
})
.catch(err => ...);
The selected answer is the best way of acheiving what I wanted - ie just using async/await - but for anyone who wants to know how I stuck with chaining, it's just breaking out of it properly. I ended up doing this by throwing an error (which is then handled elsewhere)
.then(job => {
if(!job) {
const error = new Error('Job not found');
error.statusCode(404);
throw error;
} else {
return Person.findOne(...)
}
})
.then(person => { // Person logic })
.catch(err => next(err))
So I have this function to store data in Firestore database but I wanted to check if the value exists already and based on that I want to return a boolean.
I kept trying to solve this with async await but it did not seem to work. Finally when I added a return before performanceRef.get(), it solved the issue. Even though it solved the issue I'm not clear why. I know it must have something to do with async. Can someone please explain why adding that return solved the issue?
export const createUserPerformanceDocument = async (user, currentDate, answer ) => {
const createdAt = currentDate.toLocaleDateString('en-US');
const performanceRef = firestore.doc(`performance/${user}`);
return performanceRef.get()
.then(doc => {
if(doc.exists) {
const docData = doc.data();
if (docData[createdAt]) {
return false
} else {
try {
performanceRef.set({
[createdAt]: {
Question: answer
}
}, { merge: true })
return true
} catch(error) {
console.log('Error creating performance data!', error.message);
return false
}
}
} else {
console.log("This user does not exist in the database");
}
})
}
It is not return what solved issue, probably there were some issues in your async/await code, and when you rewrote it into then callback it worked as expected.
Feel free to post your async/await version if you want more clear answer.
P.S. You don't use await keyword, so you don't need async modifier before function
Update from comments
Await keyword simply helps us stop function execution on current line and wait for promise to resolve. If you want to get result of the promise(it is named doc in your then callback) you need to store it in some constant:
const doc = await performanceRef.get()
So there you have it and can perform any kinds of validation like you do in then callback.
If you want to return validation result from this function, simply use return keyword like you already did.
I have the following chain.
return axios
.get(actionUrl, {
params: {
action: 'action3'
},
})
.finally(() => axios.get(actionUrl, {
params: {
action: 'action3'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action6'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action1'
},
}))
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain. Is it achievable without using .then and .catch and repeating the same code in them?
Thanks.
The finally function is there precisely to make sure that the function inside runs even if there is an exception. You can get the behaviour you want by using just one finally like this:
axios.get()
.then(() => doStuffOnSuccess())
.finally(() => {
axios.get().then(() => doFinallyStuff1())
.then(() => doFinallyStuff2())
.then(() => doFinallyStuff3())
.catch(e => console.error("Finally had trouble",e));
});
This way if anything within the finally function times out or fails it will break the chain. By having the final catch you will avoid it throwing back further up the chain.
This assumes that you are using finally correctly and everything in that should always get executed after the previous calls finish even if there are errors.
This is achievable with then and catch. You should not use finally if you don't want the callback to run in case of an error.
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain
So you want to not call them when the previous one fails (with a timeout), all you want to do is to ignore non-timeout errors. That's what catch should be used for:
function callEndpoint(action) {
return axios.get(actionUrl, { params: { action } }).catch(err => {
if (isTimeout(err))
throw err
else
; // ignore the error, return undefined
})
}
Then just chain them:
callEndpoint('action3').then(() => callEndpoint('action6')).then(() => callEndpoint('action3'))
Are you familiar with async/await? Generally you shouldn't chain finally like this, it's always better to create recurent function for example:
const fetchSomething = async () => {
try {
const result = await axios.get();
if (...when fetching should stop...) {
return result;
}
return fetchSomething();
} catch(error) {
return fetchSomething();
}
}
But with reccurent function is extremely important to create some kill switch to prevent executing it forever - for example set some kind of timeout, 1 minute or so and if this limit is exceeded then stop executing.
It will be probably even more easier with generators and yield but I never used this solution
Root Cause / Fix:
Turns out this was an issue with the specific winston-papertrail lib I am using and that's why it wasn't behaving as expected, even using the .finally() block.
The winston-papertrail examples didn't use end() but found the proper syntax in the upstream Winston lib examples: https://github.com/winstonjs/winston/blob/73ae01f951600306242e00dd0d2b0a85b6d9d254/examples/finish-event.js#L28
Once that was discovered, I was able to just add it to the .finally() block and everything worked fine as defined in the accepted answer
Original Post:
This is specifically talking about Javascript, or more specifically languages that do not execute operations "in-order" (async)
Taking this scenario, where the logger.close() operation must run after the logger.error() statement...
({
//SomePromise
})
.then( //Then something else )
.then(function() {logger.close();})
.catch(err =>
{
logger.error(err);
logger.close();
})
With Javascript, this works fine with try/finally:
.catch(err =>
{
try {
logger.error(err);
}
finally {
logger.close();
}
})
I'm a total newb to async, promises, etc and how to deal with them (I'm a Python guy).
Is there a more ideal way to make this work?
Am I missing an important concept about this or is this method viable and logical?
Promise.prototype.finally()
The Promise api has a finally as well.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
MDN's example:
p.finally(onFinally);
p.finally(function() {
// settled (fulfilled or rejected)
});
Applied to your code
({
//SomePromise
})
.then( //Then something else )
.catch(err => {
logger.error(err);
})
.finally(() => {
logger.close();
});
Have you heard of async await ?
const asyncFunc = async () => { something };
const logger.error = async (err) => { something with err }
try {
var result = await asyncFunc();
} catch (err) {
await logger.error(err);
} finally {
logger.close();
}
So here you will execute the asyncFunc:
If everything goes well, the finally block gets executed and the logger.close is called
If there's an error, the await keyword will wait until that
logger.error is completed and then move to the finally block which will trigger the logger.close
I am testing my Angular app using ngDescribe. I don't think ngDescribe should be too much of a problem here, as it's just managing dependency injection for me. I first began to attempt my test the way the ngDescribe docs say, in the code below I have changed it to a more direct approach just to see if I could get any changes. I am calling a method that in turn calls $http.post('urlname', data); While debugging I can clearly see that my method gets all the way to where it calls post() but then it never continues. Because of this, my test always times out.
Hopefully I've just got something simple that's wrong! Here is my code. The test that fails is "Should work", all the others pass as expected.
Please also note that this is being processed by babel, both the test and the service, before being tested.
Here is the service, it works perfectly when being used. It has a few other variables involved that I have removed, but I assure you those variables are working correctly. While debugging for the tests, I can see that the await is hit, but it never continues past that, or returns. When used in the app, it returns exactly as expected. I THINK this has something to do with ngmock not returning as it should.
async function apiCall (endPoint, data) {
if (!endPoint) {
return false;
}
try {
return data ? await $http.post(`${endPoint}`, data) : await $http.get(`${endPoint}`);
} catch (error) {
return false;
}
}
Here are the tests:
ngDescribe({
name: 'Api Service, apiCall',
modules: 'api',
inject: ['apiService', '$httpBackend'],
tests (deps) {
let svc;
beforeEach(() => {
svc = deps.apiService;
});
it('is a function', () => {
expect(angular.isFunction(svc.apiCall)).toBe(true);
});
it('returns a promise', () => {
const apiCall = svc.apiCall();
expect(angular.isFunction(apiCall.then)).toBe(true);
});
it('requires an endpoint', async () => {
const apiCall = await svc.apiCall();
expect(apiCall).toBe(false);
});
it('should work', (done) => {
deps.http.expectPOST('fakeForShouldWork').respond({ success: true });
const apiCall = svc.apiCall('fakeForShouldWork', {});
apiCall.then(() => done()).catch(() => done());
deps.http.flush();
});
},
});
The method being called, apiCall, is simply a promise that is resolved by $http.post().then(); It will also resolve false if an error is thrown.
Since deps.http.expectPOST does not fail, I can tell that the outgoing request is sent. I validated this by changing it to expectGET and then I received an error about it being a POST.
I have tried moving the flush() method to all different parts of the test method, but it seems to make no difference.
Any thoughts? Thanks so much for your help!