Break a .finally() chain - javascript

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

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

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.

Most efficient way to ensure sequential commands in 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

How to propagate resolve() in nested promises?

I'm writing a React application and in particular cases I have to resolve nested promises. The code works fine but I can't propagate the resolve() function up to the outer level, thus I'm not able to get the returning value.
This is the code:
writeData(data) {
this.store.dispatch({type: "START_LOADER"})
return new Promise((resolve, reject) => {
this.manager.isDeviceConnected(this.deviceId).then(res => {
this.manager.startDeviceScan(null, null, (error, device) => {
if (device.id === this.deviceId) {
resolve("test") // -> this is propagate correctly
device.connect().then((device) => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics()
}).then((device) => {
device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
).then(res => {
resolve("test2") // -> this is not propagated
}).catch(error => {
reject(error.message)
})
}).catch((error) => {
reject(error.message)
});
}
});
}).catch(error => {
reject(error.message)
})
})
}
...
...
async writeAsyncData(data) {
await this.writeData(data)
}
When I call this function:
this.writeAsyncData({data}).then(response => {
// here I expect whatever parameter I have passed to resolve()
console.log(response)
})
In case I leave resolve("test") uncommented I can console.log it without any problem, but if I comment it, the resolve("test2") doesn't show in console.log and response is undefined.
How can I make sure that even the nested parameter of the inner resolve reach the console.log ?
To nest promises properly, you do NOT wrap them in yet another manually created promise. That is an anti-pattern. Instead, you return the inner promises and that will then chain them. Whatever the inner-most promise returns will be the resolved value for the whole chain.
In addition, when you have any asynchronous operations that return callbacks, you must promisify them so that you are doing all your asynchronous control flow with promises and can consistently do proper error handling also. Do not mix plain callbacks with promises. The control flow and, in particular, proper error handling gets very, very difficult. One you start with promises, make all async operations use promises.
While this code is probably simplest with async/await, I'll first show you how you properly chain all your nested promises by returning every single inner promise.
And, to simplify your nested code, it can be flattened so that rather than each level of promise making deeper indentation, you can just return the promise back to the top level and continue processing there.
To summarize these recommendations:
1. Don't wrap existing promises in another manually created promise. That's a promise anti-pattern. Besides being unnecessary, it's very easy to make mistakes with proper error handling and error propagation.
2. Promisify any plain callbacks. This lets you do all your control flow with promises which makes it a lot easier to avoid errors or tricky situations where you don't know how to propagate errors properly.
3. Return all inner promises from within the .then() handlers to properly chain them together. This allows the inner-most return value to be the resolved value of the whole promise chain. It also allows errors to properly propagate all the way up the chain.
4. Flatten the chain. If you have multiple promises chained together, flatten them so you are always returning back to the top level and not creating deeper and deeper nesting. One case where you do have to make things deeper is if you have conditionals in your promise chain (which you do not have here).
Here's your code with those recommendations applied:
// Note: I added a timeout here so it will reject
// if this.deviceId is never found
// to avoid a situation where this promise would never resolve or reject
// This would be better if startDeviceScan() could communicate back when
// it is done with the scan
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
writeData(data) {
this.store.dispatch({type: "START_LOADER"});
return this.manager.isDeviceConnected(this.deviceId).then(res => {
return this.findDevice();
}).then(device => {
return device.connect();
}).then(device => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics();
}).then(device => {
return device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
}).then(res => {
return "test2"; // this will be propagated
});
}
Here's a version using async/await:
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
async writeData(data) {
this.store.dispatch({type: "START_LOADER"});
let res = await this.manager.isDeviceConnected(this.deviceId);
let deviceA = await this.findDevice();
let device = await deviceA.connect();
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
await device.discoverAllServicesAndCharacteristics();
let res = await device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
return "something"; // final resolved value
}
Note: In your original code, you have two overriding definitions for device. I left that there in the first version of the code here, but changed the first one to deviceA in the second one.
Note: As your code was written, if this.manager.startDeviceScan() never finds a matching device where device.id === this.deviceId, your code will get stuck, never resolving or rejecting. This seems like a hard to find bug waiting to happen. In the absolute worst case, it should have a timeout that would reject if never found, but probably the implementation of startDeviceScan needs to communicate back when the scan is done so the outer code can reject if no matching device found.
Note: I see that you never use the resolved value from this.manager.isDeviceConnected(this.deviceId);. Why is that? Does it reject if the device is not connected. If not, this seems like a no-op (doesn't do anything useful).
Note: You call and wait for device.discoverAllServicesAndCharacteristics();, but you never use any result from it. Why is that?

How to efficiently call setState on both cases - succes and error?

I'm using react and I have an asynchronous action that receives some data from API using axios. I also have a flag (state variable tableLoaded) which describes if data is fetched.
this.props.fetchDataAction(requestParams).then(
() => {
this.setState({
data: this.props.reports.data
});
}
).then(() => {
this.setState({ tableLoaded: true })
});
I want my flag tableLoaded to be set to true in both cases - either after API call succeded and failed, so I just added another then() on my Promise, which triggers function that sets this flag to true.
My question is - is this the best solution to achieve my goal? Or should I repeat this code in both cases?
You should use the Promise.finally syntax.
this.props.fetchDataAction(requestParams)
.then(() => {
// Do your thing on success
this.setState({
data: this.props.reports.data
});
})
.catch((error) => {
// Do something if failed
})
.finally(() => {
// Do this in all cases..
this.setState({ tableLoaded: true })
});
Edit:
If the return from fetchDataAction is an Axios promise, then you should replace .finally by .then because Axios doesn't offer the finally method. I would then say that your original suggestion was correct. You could comment the second .then so you know why.
this.props.fetchDataAction(requestParams)
.then(() => {
// Do your thing on success
this.setState({
data: this.props.reports.data
});
})
.catch((error) => {
// Do something if failed
})
.then(() => { // Triggered in all cases by axios
// Do this in all cases..
this.setState({ tableLoaded: true })
});
You can use all() to catch success and failures
One issue you'll run into with the current approach is that any possible errors will prevent the last .then from running, making it possible for tableLoaded to remain false if something does go wrong. See this pen for an example of this issue.
Promise.finally is one good way to step around this, as another answer points out, but my personal preference would be to use async/await.
try {
await this.props.fetchDataAction(requestParams)
this.setState({
data: this.props.reports.data
})
} catch (error) {
// handle error
}
this.setState({ tableLoaded: true })

Categories

Resources