0

I have three functions functionA, functionB, and functionC.

Why does the .then(id)... block inside functionC run even though functionB throws an error and ends with the catch statement.

I'm sure I'm missing something important about promises.

    const functionA = () => {
      return fetch("http://localhost:3001/types")
        .then((response) => response.json())
        .then((data) => data)
        .catch((error) => error);
    };

    const functionB = (type) => {
      return functionA()
        .then((response) => response.filter((item) => item.name === type))
        .then((result) => {
          if (result.length !== 1) { // assume for this example this is truthy
            const error = new Error("no type found or found more than one");
            throw error;
          } else {
            return result[0].id;
          }
        })
        .catch((error) => error); // this runs as expected since we're throwing an error above
    };
    

    const functionC = (x, y) => {                
      return functionB(y)
        .then((id) => { //why does this block run? 
          
                       //..do something
                      })
           
        .catch((error) => console.log("functionB threw an error"));
    };
yumba
  • 1,056
  • 2
  • 16
  • 31
  • 4
    Exactly *because* you have *caught* the error, which allows the chain to continue successfully. – deceze Aug 14 '21 at 11:26
  • 1
    Side note: There's never any reason for `.then((data) => data)`. It just introduces an extra async resolution without changing anything. – T.J. Crowder Aug 14 '21 at 11:35

1 Answers1

4

Your catch handler converts rejection into fulfillment with the error value. Just remove the .catch((error) => error); entirely, so that the rejection propagates to the caller.

This (which is basically what you have, just spread across two functions):

doSomething()
.then(x => { /* ...do something with x...*/ })
.catch(error => error)
.then(y => { /* ...do something with y...*/ });

is roughly analogous to this synchronous code:

let y;
try {
    const x = doSomething();
    y = /* ...do something with x... */;
} catch (error) {
    y = error;
}
/* ...do something with y... */;

Catching the error and then completing normally suppresses the error.

In general, non-async code using promises should do one of two things:

  1. Handle the error locally — this is usually only top-level entry points like your main function (if you have one) or event handlers.

  2. Return the result of calling then without using catch, so that rejections propagate to the outermost level (where, hopefully, they're handled because of #1 above).

FYI, that's basically what async/await does automatically for you (ignoring a lot of details). Just for what it's worth, here's that code (slightly modified) using async/await, assuming functionC is the top-level entry point where errors should be handled:

const functionA = async () => {
    const response = await fetch("http://localhost:3001/types");
    if (!response.ok) { // (You're missing this check)
        throw new Error(`HTTP error ${response.status}`);
    }
    return response.json();
};

const functionB = async (type) => {
    const result = (await functionA()).filter(item => item.name === type);
    if (result.length !== 1) {
        const error = new Error("no type found or found more than one");
        throw error;
    }
    return result[0].id;
};

const functionC = async (x, y) => {
    // I'm assuming this is your top-level entry point, so we'll handle
    // errors here
    try {
        const id = await functionB(y);
        //..do something
    } catch (error) {
        console.log("functionB threw an error");
    }
};
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875