Dieguinho
Dieguinho

Reputation: 788

javascript promise handling, fail to handle error

I'm having some trouble understanding what I'm doing wrong. I have a function that receives a url to which should make a GET request, in case of success should fill a combo with the received data (this depends which function calls it), in case of fail it should execute some common code.

    getFirstCombo = () => {
        this.getFromApi('/First/GetAll')
            .then(data => this.setState({firstComboOptions: this.parseCombo(data)}))
            .catch(error => console.log('ERROR2: ', error));
    }

    getSecondCombo = () => {
        this.getFromApi('/Second/GetAll')
            .then(data => this.setState({secondComboOptions: this.parseCombo(data)}))
            .catch(error => console.log('ERROR2: ', error));
    }

    parseCombo = (data: any) => {
        const combo = data.map(item => (
            { label: item.description, value: item.id }
        ));
        return combo;
    }

    getFromApi = (url: string) : Promise<any> => {
        return restApiAxios.get(url)
        .then(response => {
            return response.data;
        })
        .catch(error => {
            console.log('ERROR: ', error);
        });
    }

this code is executed on the componentDidMount of the react component, but when it fails, it first prints :

ERROR:  Error: Network Error
    at createError (createError.js:16)
    at XMLHttpRequest.handleError (xhr.js:83)

and immediately after:

PanelDatos.tsx:50 ERROR2:  TypeError: Cannot read property 'map' of undefined
    at PanelDatos.parseCombo (PanelDatos.tsx:55)
    at PanelDatos.tsx:50

so, when failing executes the catch block from getFromApi and then it tries to execute the then block in getFirstCombo, which triggers the catch block from the same function cause data does not exist, why is that? shouldnt it just execute the first catch? thanks in advance

Upvotes: 2

Views: 336

Answers (2)

palaѕн
palaѕн

Reputation: 73896

This is happening since inside getFromApi catch method on the error you are not returning anything, so by default, it is returning a resolved promise with null response and the execution goes inside getFirstCombo then method, causing another error. You can update your code to resolve this like:

getFromApi = (url: string): Promise<any> => {
   return restApiAxios.get(url)
      .then(response => response.data)
      .catch(error => Promise.reject(error));
}

The static Promise.reject function returns a Promise that is rejected. So, it will go directly into catch of wherever getFromApi is called.

DEMO:

async function getFromApi(url) {
   return fetch(url) // rejects
      .then(response => response.json())
      .catch(err => Promise.reject(err))
}
async function getFirstCombo() {
   getFromApi('https://no-such-server.abcd')
      .then(data => console.log('data: ', data))
      .catch(error => console.log('ERROR2: ', error));
}
getFirstCombo()

DEMO #2 (With getFirstCombo function not having any catch block) :

async function getFromApi(url) {
  return fetch(url) // rejects
    .then(response => response.json())
    .catch(err => {
      console.log('ERROR in getFromApi(): ', err);
      return null;  // return null, empty array, 0 or false... as per your requirement
    })
}
async function getFirstCombo() {
  getFromApi('https://no-such-server.abcd')
    .then(data => console.log('data: ', data))
    // Same value set in catch block of getFromApi will return in this then() block
    // Validate this `data` variable before processing it further like:
    // if(data === null) this means an error had occurred
    // else continue with your logic
}
getFirstCombo()

Upvotes: 1

Asher Gunsay
Asher Gunsay

Reputation: 269

.catch returns a promise much like .then, allowing you to return a custom value and handle it that way.

Try doing the following to observe the effect:

Promise
  .reject(1)
  .catch(e => e) // Catch the error and return it
  .then(console.log) // will log 1 to the console

This means you'll need to add some checks if you want to continue to use promises like this:

Promise
  .reject(new Error('haha'))
  .catch(err => ({err}))
  .then(({err, data}) => {
    if(err) return // Do nothing
    // enter code here
  })

However, using async / await will improve readability even more:

getFirstCombo = async () => {
  let response
  try {
    response = await this.getFromApi('/First/GetAll')
  } catch (e) {
    return // Exit early
  }
  let parsed
  try {
    parsed = this.parseCombo(data)
  } catch (e) {
    console.log(e)
    return // Exit early
  }
  return this.setState({firstComboOptions: parsed})
}

And, of course, throw the error again in your catch block in your api to allow it to handle api calls.

Upvotes: 2

Related Questions