Most efficient way to ensure sequential commands in Javascript - javascript

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

Related

Promise chaining and conditionals

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

Break a .finally() chain

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

original function been called instead of spied function in jest

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.

getting value from a chained promises

im really new to this of promises and im getting headaches trying to understand this, so now im trying to get an answer from a method that returns a promise, then i catch the value in a conditional and make some other operations
let addService = async(req, res) => {
checkCategoryExists(param).then(result => {
if(result){
// code here
}
}).catch(err => console.log(err));
}
let checkCategoryExists = async(param) => {
let docs = db.collection(collectionName).doc(param);
docs.get()
.then(categoryDoc => {
if(categoryDoc.exists){
if(categoryDoc.data().param== param){
return true;
}
} else {
return false;
}
})
.catch(err => false);
}
the method "checkCategoryExists" is a query to a firestore db. When i tried to check if result variable is true or false, it happens to be undefined. its not with ".then()" that i get to catch the value from the returned promise? if someone can help me, thanks in advance
So as mentioned above I think your issue is based on not returning the results of your document search so both examples below handle that.
I also noticed that you were using async tags on your functions but not ever using await so I wanted to give examples of both ways of doing it in case you wanted to use Async/Await but weren't certain how.
In the promise chain example I'm relying on the syntax of arrow functions to create the returns (no {} means return right side of equation) since none of your given code requires data manipulation before return (if your actual code needs that you should of course use brackets and remember your return statement :D )
If you choose to use Async/Await you can structure the code much more closely to synchronous examples and use try catch statements. Sometimes I find this syntax more clear if I have to do a lot of processing/manipulation before returning a result.
Good Luck :)!
// Promise Chains
const addService = (req, res) =>
checkCategoryExists(param)
.then(result => /* do stuff */)
.catch(err => console.error(err.message));
const checkCategoryExists = param =>
db.collection(collectionName.doc(param)
.get()
.then(categoryDoc =>
Promise.resolve((categoryDoc.exists && categoryDoc.data().param === param))
);
// OR using Async/Await
const addService async (req, res) => {
try {
const catExists = await checkCategoryExists(param);
if (!catExists) {
throw new Error('error code');
}
// Do Stuff.
} catch (error) {
console.error(error);
}
};
const checkCategoryExists = async param => {
try {
const docs = await db.collection(collectionName)
.doc(param)
.get();
return (docs.exists && docs.data().param === param);
} catch (error) {
console.error(error)
}
}

What might happen if I call `AsyncStorage.setItem` without await in non async function?

I am using fetch method to get some data from the server. Once I get that data, I need to store some of it (access_token to be more precise cause I am using oauth) in AsyncStorage. I tried doing just AsyncStorage.setItem without await, not how it is shown in https://facebook.github.io/react-native/docs/asyncstorage, and it worked just fine.
I changed it to:
fetch ('site/login', POST ...)
.then((response) => response.json())
.then(async(responseJson) => {
if (user.valid)
await AsyncStorage.setItem('access_token', responseJson.token);
And it works fine too. But I have 2 questions now:
Is my implementation with fetch and async correct?
And what might happen if I don't use await/async in this case?
Sorry, I am kinda new to Promises and Asynchronous methods in Javascript. Thanks!
async/await is just syntactic sugar over Promises. You're already using Promises, so there's no need to do that. Just return the Promise:
fetch ('site/login', POST ...)
.then((response) => response.json())
.then((responseJson) => {
if (user.valid) { // not sure where 'user' came from, but whatever
return AsyncStorage.setItem('access_token', responseJson.token);
} else {
throw new Error('Invalid user');
}
})
.then(_ => { // storage set, don't care about return value
// do stuff
})
.catch((err) => {
// handle error, including invalid user
});
Response to question in comments
The above in async/await would look like this:
async function foo() {
try {
const response = await fetch('site/login', POST ...);
const responseJson = await response.json();
if (user.valid) {
return await AsyncStorage.setItem('access_token', responseJson.token);
} else {
throw new Error('Invalid user');
}
} catch (error) {
// deal with errors
}
}

Categories

Resources