Will Collins
Will Collins

Reputation: 23

Converting jQuery ajax to fetch

I have this piece of code that calls a function getTableData and expects a Promise in return.

function populateTableRows(url) {
  successCallback = () => { ... };
  errorCallback = () => { ... };

  getTableData(url, successCallback, errorCallback).then(tableData => {
    // do stuff with tableData
  }
}

This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)

In getTableData, I'm currently using $.ajax like so

function getTableData(url, successCallback, errorCallback) {
  successCallback = successCallback || function() {};
  errorCallback = errorCallback || function() {};

  const ajaxOptions = {
    type: 'POST',
    url: url,
    dataType: 'json',
    xhrFields: {
      withCredentials: true
    },
    crossDomain: true,
    data: { // some data }
  };

  return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}

This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).

When converting the request over to use fetch, I have something like this

function getTableData(url, successCallback, errorCallback) {
  successCallback = successCallback || function() {};
  errorCallback = errorCallback || function() {};
  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    credentials: 'include',
    body: { // some data }
  })
  .then(response => {
    let json = response.json();
    if (response.status >= 200 && response.status < 300) {
      successCallback(json);
      return json;
    } else {
      return json.then(error => {throw error;});
    }
  }).catch((error) => {
    errorCallback(error);
    return
  });

Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.

Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.

Upvotes: 2

Views: 1187

Answers (2)

Tobias K.
Tobias K.

Reputation: 3082

In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.

  .catch((error) => {
    errorCallback(error);
    return Promise.reject();
  });

should be enough to keep the returned Promise rejecting.

Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:

  var reqPromise = fetch(url, {
    // ...
  })
  .then(response => {
    // ...
      return json.then(error => {throw error;});
  });
  
  reqPromise.catch((error) => {
    errorCallback(error);
    return
  });
  
  return reqPromise;

Upvotes: 1

Andrei Duca
Andrei Duca

Reputation: 392

When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.

For example:

apiCall()
  .catch((error) => {
    console.log(error);
    return true; // error handled, returning true here means the promise chain can continue
  })
  .then(() => {
    console.log('still executing if the API call fails');
  });

What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.

apiCall()
  .catch((error) => {
    console.log(error); // "handled", but we're still not done
    throw error; // instead of returning true, we throw the error further
    // 👆 this can also be written as `return Promise.reject(error);`
  })
  .then(() => {
    console.log('not executing anymore if the API call fails');
  })
  .catch((error) => {
    // handle the same error we have thrown from the previous catch block
    return true; // not throwing anymore, so error is handled
  })
  .then(() => {
    console.log('always executing, since we returned true in the last catch block');
  });

By the way, what you return from one then/catch block, the following one will get it as a param.

apiCall()
  .then((response) => {
    /* do something with response */;
    return 1;
  })
  .catch((error) => { return 'a'; })
  .then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise

Upvotes: 2

Related Questions