Search code examples
javascriptecmascript-6promisees6-promise

Handling & Returning Multiple Promises


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

Solution

  • 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.