-1

Consider the following code where I tried to shield fetch against any unsuccessful connections (I call them non "200-ish" in the comments) and provide a function that will make use of its successful results:

const callApi = () => {
  return fetch("http://doesnotexist.example.com")
    .then((r) => {
      // check for non200-ish respnses (404, etc.)
      if (!r.ok) {
        console.log(`status for failed call: ${r.status}`);
        throw new Error(`${r.statusText} (${r.status})`);
      } else {
        // continue the chain because the result is 200-ish
        return r;
      }
    })
    .then((r) => r.json())
    .catch((err) => {
      // should catch network errors (DNS, etc.) as well as replies that are not 200-ish
      console.log(`call failed: ${err}`);
    });
};

callApi().then((r) => console.log("the call was successful"));

The result is

call failed: TypeError: Failed to fetch
the call was successful

Since this is a network issue, the first then() was not executed and we jumped directly to the catch(). But why has the last then() been executed?

The next example is for a call that returns an error code:

const callApi = () => {
  return fetch("https://httpstat.us/500")
    .then((r) => {
      // check for non200-ish respnses (404, etc.)
      if (!r.ok) {
        console.log(`status for failed call: ${r.status}`);
        throw new Error(`${r.statusText} (${r.status})`);
      } else {
        // continue the chain because the result is 200-ish
        return r;
      }
    })
    .then((r) => r.json())
    .catch((err) => {
      // should catch network errors (DNS, etc.) as well as replies that are not 200-ish
      console.log(`call failed: ${err}`);
    });
};

callApi().then((r) => console.log("the call was successful"));

The output is

status for failed call: 500
call failed: Error: Internal Server Error (500)
the call was successful

Same question as above.

Finally, for 200 everything is fine:

const callApi = () => {
  return fetch("https://httpstat.us/200")
    .then((r) => {
      // check for non200-ish respnses (404, etc.)
      if (!r.ok) {
        console.log(`status for failed call: ${r.status}`);
        throw new Error(`${r.statusText} (${r.status})`);
      } else {
        // continue the chain because the result is 200-ish
        return r;
      }
    })
    .catch((err) => {
      // should catch network errors (DNS, etc.) as well as replies that are not 200-ish
      console.log(`call failed: ${err}`);
    });
};

callApi().then((r) => console.log("the call was successful"));

Another way to address the question would be: how to stop processing at the catch()?

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • Because you explicitly `.catch` the fail, which then allows the promise chain to continue with the next `.then`. – deceze Jun 22 '22 at 10:16
  • 1
    If you convert your code to normal try catch, the exact same thing would apply. `try { doSomething(); } catch(e) { console.log('oops'); }; console.log('I will log'); `, so in a normal try / catch you would do -> `catch(e) { console.log('oops'); throw e; }` if you still wanted the error to propagate. – Keith Jun 22 '22 at 10:20
  • You cannot stop a promise chain from being processed. The promise must resolve. Leaving a promise dangling is not desirable. A promise can either succeed or fail. Just let it fail by not catching. The caller is then also in control of handling fails instead of being left with an unresolved promise. – deceze Jun 22 '22 at 10:22
  • @deceze: (copied from the comment to the answer) I was hoping for a way to say "do the `then()` only if the call is successful" and completely manage the error in the function – WoJ Jun 22 '22 at 10:24
  • @WoJ You can wrap the hole fetch with new promise and resolve it in the last then – angel.bonev Jun 22 '22 at 10:26
  • 1
    Then what is the caller supposed to do when the request fails? Wait forever? For example, you might do something with the UI while the request is going on: show some spinner and "please wait…" text, do the request, when it finishes, show the data. If the caller gets no feedback about failed calls, the UI will simply be stuck. – deceze Jun 22 '22 at 10:27
  • @deceze he will show that message in the catch or find a way to resolved then, but he wants the `then` to be executed only if the everything is ok – angel.bonev Jun 22 '22 at 10:30
  • 3
    @angel.bonev You're going for some API abstraction here. The function `callApi` just cares about calling the API and returning the data. It may output some console messages for debugging. **But it should probably not care about UI updates.** Because this function may be called from several different places in the UI, and it cannot know nor should it know what UI needs to be updated in relation to it. That's what the code calling this function should care about. — I mean, you can of course mix all this together willy-nilly, enjoy your spaghetti… – deceze Jun 22 '22 at 10:32
  • @deceze wow just wow. So that was a trick question? So how about that reject in that promise ? The callApi will only do calling api but will resolve only if it's correct he can handle any errors in catch after that something like this `callApi().then(...).catch(//do what event you whant with that error)` . But i like the given answer better. So cheers – angel.bonev Jun 22 '22 at 11:41
  • @angel.bonev Not "trick question", but *rhetorical device.* And the point is that with the current code and OP's desired behaviour, `callApi().catch(..)` is impossible. – deceze Jun 22 '22 at 11:42
  • @deceze we agree on that – angel.bonev Jun 22 '22 at 12:08

1 Answers1

3

You're returning the result of a fetch().then().catch() chain, and calling a .then() on that:

callApi().then((r) => console.log("the call was successful"));

That last .then() will always be executed, because the promise was handled successfully. It either:

  • Completed successfully, or
  • catch took care of any errors that occurred`
Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • Thank you. In that case, how can I stop the processing at the `catch()`? – WoJ Jun 22 '22 at 10:18
  • 1
    (Re-)Throw an error. – Cerbrus Jun 22 '22 at 10:18
  • Just to add in order to propagate the errors, you need to throw the error from your catch block as well if you want to do that. – Pranay Tripathi Jun 22 '22 at 10:19
  • OK, but this means I have to catch it again, as part of calling the function. I was hoping for a way to say "do the `then()` only if the call is successful" and completely manage the error in the function – WoJ Jun 22 '22 at 10:23
  • Why would you need to catch it again, @WoJ? If you throw an error in the function, all that does is prevent the outer `then` from being executed – Cerbrus Jun 22 '22 at 10:38
  • Mostly for aesthetical reasons - so that the console is clean. With all information gather so far from your answer and comments, I will add a `catch(() => {{}})` after the last `then()` to keep the console clean - I was just hoping there would be a way to avoid adding this to each of the `callApi` calls. – WoJ Jun 22 '22 at 10:41
  • 1
    @WoJ Your console might now be clean, but your code has a really bad smell.. :) Personally I would rather keep code correct, and let exceptions do what they were designed for. – Keith Jun 22 '22 at 10:52