3

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...
Ben Carey
  • 16,540
  • 19
  • 87
  • 169
  • 1
    Avoid the [promise anti-pattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it/23803744#23803744). You can just do `return this.axios.request(request).catch(...)`. – jfriend00 Feb 21 '18 at 18:11
  • @jfriend00 That is one of the reasons I am asking this question, I *hate* my code above, I know it is bad but I don't know what the solution is. Would you be able to enlighten me? – Ben Carey Feb 21 '18 at 18:13
  • 1
    What does *"and prevent subsequent calls to `then` and `catch`"* mean? Your functions are returning promises. By definition, `then` and `catch` handlers will get run. – T.J. Crowder Feb 21 '18 at 18:15
  • @jfriend00 That is what I tried to do before, but I then had problems with `catch` and `then` being called from the calling classes. Is there a way to terminate the promise? – Ben Carey Feb 21 '18 at 18:15
  • 1
    Well, a bunch of your question doesn't make sense which is why I didn't attempt an answer. As TJ said, if you're returning a promise you don't prevent `.then()` and `.catch()` handlers from running. Instead, you train the calling code what to act on and what to skip and you make sure you set a resolved value or reject reason that causes the caller to do the right thing. – jfriend00 Feb 21 '18 at 18:17
  • No, you don't terminate a promise. You resolve or reject it and create an understanding with the caller about what the caller should be looking for to act on. The concept is no different than a synchronous function returning a value. You don't stop execution of the call chain. You train the caller what to do for various return values. – jfriend00 Feb 21 '18 at 18:19
  • Hi @T.J.Crowder, hope you are well mate :-). I am struggling to explain what I am trying to achieve so will do my best here... Essentially, I want to call the `axios.request()` in the `API` class, and then only run the `then` and `catch` on the returned promise if it did not have errors in the `API` class, and then again, for the `Form` class. – Ben Carey Feb 21 '18 at 18:21
  • That's just not an appropriate of feasible design. If you want to create a resolved value (such as `null`) or a particular reject reason that tells the caller to ignore it, then you can do that by creating a convention that the caller looks for. But, you don't prevent their handlers from ever running. – jfriend00 Feb 21 '18 at 18:22
  • @jfriend00 Much much clearer now... I haven't touched Javascript in 8 months so needed a bit of friendly support on this whole Promise business. Thank you very much, really appreciate your points :-D – Ben Carey Feb 21 '18 at 18:29
  • @BenCarey, in addition to TJC's answer, you might like to be aware of the issues discussed [here](https://stackoverflow.com/q/32032588/3478010). – Roamer-1888 Feb 22 '18 at 02:04

1 Answers1

3

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:

  1. Convert rejection into resolution
  2. Convert rejection with one error into rejection with another error
  3. 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:

  1. Convert resolution with one value into resolution with another value
  2. Convert resolution into rejection
  3. 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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    This is a perfect explanation TJ, thank you very much! Really appreciate it! I will give my implementation another go and come back if I have any questions but this seems to have covered everything I need :-D – Ben Carey Feb 21 '18 at 18:25