Extracting Promise Chain to an external function - javascript

So Promises make everything so much cleaner and all but how do I extrapolate the end of a promise chain to an external function?
For example:
SomePromise.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
SomePromise2.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
SomePromise3.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
Somehow, I'd like to extract the then and catch so that I can pass a function into the then like such:
SomePromise.then(handler);
SomePromise2.then(handler);
SomePromise3.then(handler);
I suppose I could extract the entire Promise into a function and pass parameters to and from there but that feels a bit clunky. Any elegant solutions?
Edit: I should elaborate that I'm focusing on the catch part. Imagine multiple different queries with identical data-parsers and error handlers.
Edit 2: In hindsight I realize now that I could just extrapolate
handler = () => {}
errorHandler = () => {}
Promise.then(handler)
.catch(errorHandler)
but the

let p1=Promise.resolve(1);
function handler(data){console.log(data)};
function errorHandler(error){console.log(error)};
p1.then(handler,errorHandler);
Multiple things here, your code you are expecting is:
SomePromise.then(handler);
However it should be in actual as
function handler(data){};
SomePromise.then((data)=>handler(data));

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.

Using promise after iteration is finished

I am trying to figure out how implement an asynchronous way of executing after an iteration is done
Essentially I have something like this:
data.businesses.map( club => {
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
})
// when data.businesses.map is done iterating all of the elements, do something
As you can see, there is some asynchronous issue when retrieving fetching information via axios
I'm still new to the idea of Promises but I am not sure where to apply, if it applicable
You can wait for a collection of promises to resolve (or at least one to reject) using Promise.all().
To gather that collection, you can have the iterator function for .map() return each Promise by removing the braces from the arrow function.
let promisedClubs = data.businesses.map( club =>
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
);
Promise.all(promisedClubs).then(clubs => {
// when data.businesses.map is done iterating all of the elements, do something
}, failedClub => {
// error handling
});

How can you modify express.res within bluebird promises, and use res within another promise?

I might be overthinking this, but bear with me... If I have the following code
app.get('/api/endpoint', function(req, res, next) {
new Promise(function() {
// doing something that takes lots of code
return someJson
})
.then(function(someJson) {
// analyze someJson with lots of code
})
.then(function() {
// do more
})
// chain a bunch more promises here
.then(function() {
res.status(200).send(message)
})
.catch(function(err) {
// error handling
})
.finally(function() {
// clean up
})
})
If the promise chain gets very long, it can be a pain to navigate the endpoint. So, I want each step of the promise to be it's own function (to simplify the above code). So I can rewrite the top 2 promises as so:
function findSomeJson() {
return new Promise(function() {
// doing something that takes lots of code
return someJson
})
}
function analyzeSomeJson(someJson) {
return new Promise(function(someJson) {
// analyze someJson with lots of code
})
}
Now, each of these functions can be used in the original example like so:
findSomeJson()
.then(function(someJson) {
return analyzeSomeJson(someJson)
})
// etc...
But, what happens if I need to tweak res within those promises? Do I need to return res every time and store someJson within res? And, what happens if I have to use next()? How do I ensure res is modified at the end of my promise chain? I can't do that in finally(), do I have to do it in my last promise?
If you really want to be able to mutate res from within your functions you have to pass it around. I think the easiest/cleanest way to pass it around is to use bind like this:
findSomeJson()
.then(analyzeSomeJson.bind(this, res))
.then(doMore.bind(this, res))
.then(andEvenMore.bind(this, res))...
Then the analyzeSomeJson definition would look like this:
// .bind makes it so that res is the first argument when it is called
function analyzeSomeJson(res, someJson) {
return new Promise(function(someJson) {
// analyze someJson with lots of code
})
}
But I would probably try to avoid passing around res so that only your controller has to know about req and res. You could move all your functions to a service or services and have the controller determine what needs to happen to res based on what the service(s) return. Hopefully that will lead to maintainable/testable code.
UPDATE very simple service example
// endpoint.js
var jsonService = require('./jsonService.js');
app.get('/api/endpoint', function(req, res, next) {
jsonService.getJson()
// chain a bunch more promises here
.then(function(json) {
if(json === null) {
return res.status(404).send('Not Found');
}
res.status(200).send(json);
})
.catch(function(err) {
// error handling
});
});
There are many ways you could implement your service but really its just another .js file that you require in to your "controller" or endpoint.js. Whatever you export will be your "public" methods.
// jsonService.js
// "public" methods
var service = {
getJson: function() {
return findSomeJson().then(analyzeSomeJson);
}
};
module.exports = service;
// "private" methods
function findSomeJson() {
return new Promise(function() {
// doing something that takes lots of code
return someJson
});
}
function analyzeSomeJson(someJson) {
return new Promise(function(someJson) {
// analyze someJson with lots of code
});
}

Categories

Resources