Handling & Returning Multiple Promises - javascript

Brief Explanation
I am currently struggling to get to grips with the structure for the following implementation:
// Method from API Class (layer for communicating with the API)
call() {
// Return axios request BUT handle specific API errors e.g. '401 Unauthorized'
// and prevent subsequent calls to `then` and `catch`
}
// Method from Form Class (used for all forms)
submit() {
// Call the `call` method on the API class and process
// the response.
// IF any validation errors are returned then
// process these and prevent subsequent calls to `then`
// and `catch`
}
// Method on the component itself (unique for each form)
onSubmit() {
// Call the `submit` method on the Form class
// Process the response
// Handle any errors that are not handled by the parent
// methods
}
I have implemented this like so:
// Method from API Class (layer for communicating with the API)
call() {
// The purpose of this is to execute the API request and return
// the promise to the caller. However, we need to catch specific
// API errors such as '401 Unauthorized' and prevent any subsequent
// `then` and `catch` calls from the caller
return new Promise((resolve, reject) => {
this.axios.request(request)
.then(response => {
resolve(response); // Do I actually need to do this?
})
.catch(error => {
// Here we need to handle unauthorized errors and prevent any more execution...
reject(error);
});
});
}
// Method from Form Class (used for all forms)
submit() {
// The purpose of this is to call the API, and then, if it
// returns data, or validation errors, process these.
return new Promise((resolve, reject) => {
api.call()
.then(response => {
// Process form on success
this.onSuccess(response.data);
resolve(response.data);
})
.catch(error => {
// Process any validation errors AND prevent
// any further calls to `then` and `catch` from
// the caller (the form component)
this.onFail(error.response.data.error.meta);
reject(error);
})
.then(() => this.processing = false); // This MUST run
});
}
// Method on the component itself (unique for each form)
onSubmit() {
this.form.submit()
.then(response => {
// This should only run if no errors were caught in
// either of the parent calls
// Then, do some cool stuff...
});
}
Questions
My comments should explain what I am trying to achieve, but just to be clear:
How do I catch certain errors, and then, prevent any further calls to then and catch from running from the calling class/component?
Is it actually necessary to create a new Promise each time I return one?
I know that axios.request already returns a Promise but I don't know how to access the resolve and reject methods without wrapping it with a new Promise. If this is wrong, please feel free to correct...

First: There's no need for new Promise when you already have a promise to work with. So as a first step, let's fix (say) call:
call() {
return this.axios.request(request)
.then(response => {
// ...
})
.catch(error => {
// ...
});
}
How do I catch certain errors, and then, prevent any further calls to then and catch from running from the calling class/component?
You don't. If you're returning a promise, it must settle (resolve or reject). Either involves subsequent handlers running. A promise is exactly that: A promise that you'll either provide a value (resolution) or an error (rejection).
The key concept you may be missing (lots of people do!) is that then and catch return new promises, which are resolved/rejected based on what their handlers do.
You can use a catch handler to:
Convert rejection into resolution
Convert rejection with one error into rejection with another error
Base the result on the result of another promise entirely
...but you can't suppress calls to subsequent callbacks.
You can use a then handler to:
Convert resolution with one value into resolution with another value
Convert resolution into rejection
Base the result on the result of another promise entirely
So for instance, if you have an error condition that you can correct (which is relatively rare, but happens), you can do this
.catch(error => {
if (/*...the error can be corrected...*/) {
return valueFromCorrectingTheProblem;
}
throw error; // Couldn't correct it
})
If you return a value (or a promise that resolves), the promise returned by catch resolves with that value. If you throw (or return a promise that rejects), the promise returned by catch rejects.
Is it actually necessary to create a new Promise each time I return one?
No, see above. (And good question.)
I know that axios.request already returns a Promise but I don't know how to access the resolve and reject methods without wrapping it with a new Promise.
You don't; you use then and catch. They return a new promise that will be resolved/rejected according to what happens in the handler.

Related

How to use a single default catch in a double returned promise?

Basically I have a construction like;
// Library function
function foo(){
return new Promise((resolve, reject) =>{
// Do lots of stuff, like rendering a prompt
// On user action
if(userDidSomething){
resolve(user_input);
}
if(userCanceled){
// On user cancel
reject('User canceled');
}
}).catch("Default error (usually canceling whatever it is, which means; do nothing)");
}
// Function to call library function with predefined settings for this website
function bar(){
// Have some defaults set here, like which kind of prompt it should be
return foo();
}
// Where the function will actually be used
function baz(){
bar().then("do some things");
}
I've worked my way around this issue some years ago but unfortunately forgot how I actually managed to do that.
The goal: Have one standard catch handle things for me on the library level. If I want to overrule it, I can always do that later. So I guess it's: Break the chain.
The problem: Having the catch before the then, causes the then to be triggered once I have dealt with the catch (which is; ignoring it, in this case)
My current solution: I'm using throw on the library-level catch, this causes the promise to throw an uncaught error exception.
However, this completely clutters up my console with errors which aren't really errors.
The problem is that the entire resolve/reject of the promise is being handled by the library. That promise gets returned around and I only call it way later.
This is a function I'm calling about 300 times throughout my project, and I don't want to be putting custom error handling on every single one of those function calls when the handling of this should be "don't do anything, really".
What you are asking for is not really possible using native Promises because that's not how they were intended to be used.
You can, though, create a custom Promise class that does this. Note that muting all rejections is a bad idea because you won't see if your code errors out. To go around that, only a special value (SpecialPromise.CANCELED) is treated differently. The code has to track whether a .catch is attached to it. When the promise encounters the special value and it has no catch callback at the moment, it quickly attaches a no-op catch callback to silence the error:
class SilentPromise extends Promise{
constructor(executor){
super((resolve, reject) => {
executor(
resolve,
e => {
if(e === this.constructor.CANCELED && !this._hasCatch){
this.catch(() => {})
}
reject(e)
}
)
})
this._hasCatch = false
}
then(success, error){
this._hasCatch = true
return super.then(success, error)
}
catch(error){
this._hasCatch = true
return super.catch(error)
}
catchIfNotCanceled(error){
this.catch(e => {
if(e === this.constructor.CANCELED)
throw e
return error(e)
})
}
}
SilentPromise[Symbol.species] = SilentPromise
SilentPromise.CANCELED = Symbol('CANCELED')
You can convert existing promises between SilentPromise and Promise using SilentPromise.resolve() and Promise.resolve().

Success and rejection handlers added to Promise do not activate

This is a basic question. I'm working through a js/node workshop on async programming called promise-it-wont-hurt. I have the following exercise:
// Let’s build a simple script to prove to ourselves that promises may only
// resolve one time and all future attempts to resolve them will simply be ignored.
// First, create a promise using the Promise constructor as we have been doing.
// In the promise’s executor, immediately attempt to fulfill the promise with a
// value of 'I FIRED'.
// Then, after the fulfill call, immediately try to reject the promise with an
// Error created with parameter 'I DID NOT FIRE'.
// After the promise creation, create a function onRejected with one parameter
// error that prints the Error’s message with console.log.
// Lastly, pass console.log and the function you just created as the success
// and rejection handlers respectively.
// If successful, your script should only log “I FIRED” and should not log
// “I DID NOT FIRE”.
my test.js contains:
var promise = new Promise(function(resolve, reject) {
() => {resolve("I FIRED")},
() => { reject(new Error("I DID NOT FIRE"))}
});
function onReject (error) {
console.log(error.message);
}
promise.then(x =>console.log(x),error =>onReject(error))
I'm a little unsure about:
// Lastly, pass console.log and the function you just created as the success
// and rejection handlers respectively.
I've rewritten this as:
promise.then(x =>console.log(x),error =>onReject(error))
However this produces no output at the command line. Experts can you please explain where my error is?
Also new to JS Promises, but I believe this is because the resolve() and reject() are inside of their own functions that are not called.
I tried changing the promise to this:
var promise = new Promise(function(resolve, reject) {
resolve("I FIRED");
reject(new Error("I DID NOT FIRE"));
});
It seemed to have the desired efffect when I tested in the Chrome DevTools Console.
You need to return the promise.
const madeOrder = bool => new Promise((res,rej) => {
if(bool) res('HE MADE AN ORDER!');
rej('HE DIDNT MAKE AN ORDER :(');
})
madeOrder(true)
.then(res => console.log(res))
.catch(err => console.log(err));

Promise: skip all fulfill and reject reactions but execute .finally

I have function called request:
function request (endpoint) {
return axios.request(endpoint).then(api.onSuccess).catch(api.onError)
}
api.onSuccess:
onSuccess (response) {
let breakChain = false
... some logic goes here ...
return breakChain ? (new Promise(() => {})) : response
}
api.onError:
onError (error) {
let breakChain = false
... some logic goes here ...
if (breakChain) {
return new Promise(() => {})
} else {
throw error
}
}
api holds a lot of functions that represent different API calls based on provided endpoints data and return request(endpoint).
Currenly I have code, as you can see, that return Promise with empty executor that is always in pending state to stop the chain of subsequent .then(...) and .catch(...) handlers from execution as they just infinitely wait for that Promise to settle. This is done to handle certain API responses that have common response handling (like responses with code >= 500).
The problem is that now I need a call to .finally() (like in Vue cookbook - https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html#Dealing-with-Errors) to restore some component's state nevertheless of whether there is an error or not, but this approach of pending Promise is an obstacle.
The question is: is it possible to skip all subsequent .then(...) and .catch(...) calls within one of such handlers to go directly to .finally()?
Update. I haven't mentioned the important bit - api.onSuccess and api.onError are basic handlers. In another application components there are additional handlers appended to the end of that basic chain presented in request function. Usual chain of some API call has a following resulting form:
return axios.request(endpoint).then(api.onSuccess).catch(api.onError).then((response) => {...}).catch(() => {...}).finally(() => {...})
(sometimes there is no .finally() or .catch(...) block)
Is it possible to skip all subsequent .then(...) and .catch(...) calls within one of such handlers to go directly to .finally()?
No.
Currenly I stop the chain by just infinitely waiting - yet this approach of pending Promise is an obstacle.
Indeed, don't do that. You can skip then handlers by using rejections (exceptions) for flow control, but the more appropriate way is to handle this by nesting the part of the chain to be skipped inside an if statement.
This is done to handle certain API responses that have common response handling (like responses with code >= 500)
For that, you should use something like
return axios.request(endpoint).then(response => {
…
}).catch(error => {
if (api.handleCommonError(error)) return; // returns false if it couldn't handle the error
…
}).finally(() => {
…
});
Yes, you cannot hide this kind of error handling inside an api.request function.
You can use async and await. All modern browsers support them, and your bundler can make them compatible with older browsers.
For example:
async function request (endpoint) {
try {
const response = await axios.request(endpoint);
return api.onSuccess(response);
} catch (err) {
api.onError(err);
} finally {
// Always executed, even if no error was thrown
}
}
You can also do it more traditionally:
function request (endpoint) {
return axios.request(endpoint).then(api.onSuccess, api.onError).then(() => {
// Code is always executed after error / success
}
}

Is there a way to end a thenable chain?

let textProcess = new Promise((resolve, reject) => {
let text
try {
// fetch text from the internet
text = "str"
resolve(text)
} catch (e) {
reject("failed to fetch!")
}
})
textProcess.then(data => {
let json
try {
json = JSON.parse(data)
} catch (e) {
console.error("failed to parse!")
// ..........i want to end the whole process here, never go ahead
}
}, e => {
// try to fetch text from local chache
}).then(json => {
// work on the json obj
}, e => {
// if failed to fetch from local too, just let it go
})
Is there a way to end a thenable chain?
Look at the example above, I want to end the whole process when parsing is failed(the line preceeded with ".........."). But actually the last then will still be invoked though.
What is the proper and elegant way to achieve my goal?
Your Promise usage involves quite a bit of sub-optimal patterns. Fixing them actually leads to what you're trying to achieve too.
textProcess.then(data => {
// 1st anti-pattern fix
// any error triggered here
// also gets caught at the end catch
return JSON.parse(data)
}).then(json => {
// work on json obj
}).catch(e => {
// 2nd anti-pattern fix
// one catch for the whole thenable chain
console.error("Failed to parse!", e)
})
This way, you properly leverage what Javascript Promise offers, and one simple .catch for what you need.
Edit - some explanations on involved Promise anti-patterns
The marked 1st anti-pattern is about unnecessary nested try..catch block within then. Within it, you can return synchronously (even undefined), another Promise (both of these are thenable), or throw an Error (which would get caught by catch). Basically you don't need to explicitly catch it but let it "flow" through.
The 2nd anti-pattern as mentioned is the fact that the second parameter -- reject handler of then is considered sub-optimal in most use cases. A Promise chain should be leveraging one catch to simplify the workflow.
However, in the rare event of the need to perform "early catch" and "resume", consider the following way, which is still a bit clearer than using two handlers for then:
textProcess.then(data => {
return parser1(data)
}).catch(e => {
console.log("First parser failed")
// for example first parser failed
return "fallback data"
}).then(data => {
// process received data as "fallback data"
}).catch(e => {
// always have a "last resort" catch at the end of the workflow
})
The short answer is no, there is no mechanism to terminate a promise chain in a then handler part-way down the chain. (A proposal for promise cancellation was made to the TC39 committee in 2016 but was subsequently withdrawn.)
Note that a "promise chain" often refers to the promise
returned by the last then, catch or finally call in a chain of single promise method calls concatenated together.
All calls to the promise methods listed above are made synchronously when code defining the chain is executed. After execution, all promises in the chain have been created and all method calls in the chain called.
Since promises only have three states (pending, fulfilled and rejected), the best you can do is to arrange for "cancellation" to be sent down the rejection channel of linked promises and ignore it as required. (There is no standard "cancelled" rejection value to use).

Bluebird: How to bind a promise so that I can customize behavior in .catch, and then allow error to continue cascading?

Note- see the update below
I want have a flow with a dependency between the first task to the execution between the other secondary tasks, but the other secondary tasks can all be run concurrently.
I want to write a clean flow which will make it easy to handle errors, and I've tried several variations but can't get it right.
Here's what I will be working with, regardless of the pattern I compose it:
var primaryAsyncTask = {...}; //single 'task' which has an execution function
var secondaryAsyncTasks = [{...}]; //array of tasks with same format as above
function promisifyTask(task){ .... }; //returns a promise which executes the task and appends the resolve/reject functions as the last arguments to the task execution function
Now here's the two options I currently tried:
In promisifyTask, I wrap the rejection handler and bind it to the task, so that I can customize the failure info and then check it upon final catch. In other words, the flow looks like this:
.
function taskRejecter(reject, reason) {
//do something to save info from reason onto this = task
reject();
}
function promisifyTask(task) {
return new Promise(function (resolve, reject) {
var rejecter = taskRejecter.bind(task, reject);
task.execute.apply(null, task.args.concat([resolve, rejecter]));
});
}
//and then when executing:
promisifyTask(primaryAsyncTask)
.then(function () {
return Promise.settle(secondaryAsyncTasks.map(function (task) {
return promisifyTask(task);
}));
})
.then(function onSuccess() {...
})
.catch(function onFail() {
//map all the info from the failed tasks (primary/secondary) to the info saved in it by the taskRejecter.
});
The advantage here is that if the primary task fails, it doesn't execute the secondary tasks and reaches the onFail in the catch... but if it succeeds, it executes them and will reach the onFail only if one of them fails (which is also desired behavior).
Alternatively, which looks much nicer internally, is to bind the promise and catch it, instead of wrapping the rejection handler, and then I can handle everything in a single 'taskFailCatcher' function, so it would look like this:
function onTaskFail(reason){
//do something to save info from reason onto this = task, since it was bound before the catch
}
function promisifyTask(task){
return new Promise(function(resolve, reject) {
task.execute.apply(null, task.args.concat([resolve, reject]));
});
}
function promisifyTaskAndCatch(task){
return promisifyTask(task).bind(task).catch(onTaskFail)
}
//and then when executing:
promisifyTask(primaryAsyncTask)
.then(function (){
return Promise.settle(secondaryAsyncTasks.map(function (task) {
return promisifyTaskAndCatch(task);
}));
})
.then(function onSuccess(){...})
.catch(function onFail(){...})
I like the .bind.catch, but the problem here is twofold:
The flow is not consistent. I don't want to execute the secondary tasks when the primary fails, so I use promisifyTask for the primary (so that it is not caught, and reaches the catch at the end), but I promisifyTaskAndCatch inside the .settle, so that I can easily bind and edit the failure info for the task directly after the rejection.
The .catch here is reached only after the primary fails. Now that I catch all the secondary tasks, the .settle will always receive fulfilled promises, so I reach onSuccess even if a secondary task fails.
How can I edit this flow so that I make use of the best of both worlds (.bind.catch for error handling, with a consistent and clear flow)?
-----------------UPDATE------------------
I almost figured this out. I changed it by removing promisifyTaskAndCatch and changing promisifyTask to be:
function promisifyTask(task) {
var promise = new Promise(function (resolve, reject) {
task.execute.apply(null, task.args.concat([resolve, reject]));
});
promise.bind(task).catch(onTaskFail);
return promise;
}
Now the syntax is consistent and the error cascades through, and I also get my error reporting.
The only problem is now that I don't have a guarantee that the internal catch for the task will happen before the external final catch (with the onFail at the end of the chain), since this is async.
Is there a way to return the caught promise, but still have it fail? can I do this without just rethrowing an error inside of .catch ?
The only problem is now that I don't have a guarantee that the internal catch for the task will happen before the external final catch (with the onFail at the end of the chain), since this is async
Actually you do have, since the .catch(onTaskFail) is invoked first the onTaskFail would be executed before the final one. But you are right, if the final catch depends on things that onTaskFail does then it would be much cleaner to actually return a promise for that result.
Is there a way to return the caught promise, but still have it fail?
I think re-throwing the reason would be the best.
Or you even don't have them fail, and use Promise.all and inspect the task objects about their results instead of using Promise.settle and its PromiseInspection values. As you say the reason is saved on the tasks, the most consistent flow would be to return the task objects:
function onTaskFail(reason){
this.error = reason; // or so
return this;
}
runTask(primaryAsyncTask).then(function(primResult) {
return Promise.all(secondaryAsyncTasks.map(function (task) {
return runTask(task).bind(task).catch(onTaskFail);
}));
}).then(function(taskResults) {
// test for errors in the secondary results
// and throw them if you want the global onFail
// else do onSuccess()
}).catch(onFail);
can I do this without just rethrowing an error inside of .catch ?
Yes, you can also return a rejected promise. That might be a Promise.reject(reason), but simpler might be to return promise that is already there (though not currently in scope of onTaskFail). The .return() helper method could be used here though:
…
return promise.bind(task).catch(onTaskFail).return(promise);

Categories