EDIT: the conclusion reviewers came to about this being a duplicate is wrong and stems from misunderstanding of the actual question. Please see the EDIT at the bottom of the post for a summary and resolution, as well as the comments to this question confirming that this is not in fact a duplicate.
ORIGINAL QUESTION: I'm handling the logic for backend communication for our team's frontend. All requests follow this pattern:
- take input (varies)
- preprocess the input (consistent for all inputs)
- send input to the backend (varies, has developer-defined conditional logic)
- postprocess backend response (consistent for all)
- pass response to the rest of the frontend
We recently stumbled into a few calls that were failing silently, upon investigation we realized the issue was that the way the logic was originally written (via promises) required an individual .catch() call, which we missed in a few places.
While looking at this module I also noticed that it's accumulating a lot of boilerplate, mainly every single call to the backend is written as:
function performOperation(input) {
input = preprocess(input); // step 2
return new Promise((resolve, reject) => {
if (env.local) {
resolve(/* logic to pull from local cache */);
} else {
thirdPartyFetchCall(input).then((response) => {
resolve(response);
}).catch((error) => {
reject(error);
});
}
}).then((output) => {
return postprocess(output); // step 4
}).catch((error) => {
// this is for catching postprocess errors
console.error(error);
});
}
There are several dozen calls following this pattern and I wanted to clean it up a bit, as well as making the final error dump automatic (preprocess benefits from being in the same thread, so its throws are automatically logged, that's not the case with postprocess).
My idea was wrapping it in a promise factory that the developer could then call as follows:
function performOperation(input) {
return factory(input).send((processedInput) => {
if (env.local) {
return (/* logic to pull from local cache */);
} else {
return thirdPartyFetchCall(processedInput);
}
});
}
The promise factory would take care of preprocess and postprocess calls, as well as dumping the postprocess error. Problem is that I'm not sure how to tell from the factory if thirdPartyFetchCall
will result in resolve
or reject
while keeping the logic independent. I could call .then
and .catch
on the return of the function being passed to .send
but in the case of env.local
, it will return a non-promise value and convincing other developers that promisifying it manually is less error-prone than remembering to add the .catch
at the end of the chain is unlikely. How do you recommend I handle it safely from the factory, just test if the object is a promise? If I simply call resolve
on it from within the factory, will the .catch
clause still fire in response to an error thrown by thirdPartyFetchCall
without an explicit reject
call in the Promise declaration? Here is an example of the factory code to go along with the above:
function factory(input) {
return new Promise((resolve, reject) => {
return { send: (request) => {
resolve(request(preprocess(input)));
}
}).then((output) => {
return postprocess(output);
}).catch((error) => {
console.error(error);
});
}
Note that in above factory reject
never gets explicitly called in the promise declaration, but I still want the catch
at the end of the chain to execute in response to an error thrown within postprocess
call or a reject
call within thirdPartyFetchCall
promise returned by the request
function.
EDIT: Ok, I feel stupid now. The whole factory idea led me astray. I got stuck in implementation details when really my main question was whether .catch()
clause would still fire on a promise where reject has not been explicitly called within the declaration. And the answer is yes, it will - which I was able to test by simplifying the test further and running it in node:
function test() { throw 'error'; }
let p = new Promise((resolve, reject) => {
resolve(test());
}).then((r) => {
console.log('success');
}).catch((r) => {
console.log('error');
});