1

I'd like to use a Promise chain to load some data before starting my application. So, I'll have something like:

fetch('path/to/data.json')
  .then((response) => response.json())
  .then((data) => {
    console.log(`Hey cool, we're done w/ the promise chain here`);
    startApp(data);
  })
  .catch((error) => console.error(`Error in promise chain: ${error}`));

It does work—but with this setup, any errors in my startApp function (or any subsequent functions down the line) get handled within the Promise chain, which seems wrong.

The Promise examples I find typically end with a console log in the last then() of the chain, so they're not terribly instructive about this.

  • Is the way I've set things up actually fine?
  • If not, how can I wait til the end of the promise chain to call the startApp function without still being inside the chain?
Scott Thiessen
  • 873
  • 7
  • 20

2 Answers2

1

As per the effects observed in the question, Promise implementation calls a .then handler within a try/catch block. If an error is caught, the promise returned by then is rejected for the same reason.

To report application errors separately you could place your own try/catch block around the application and handle error reporting explicitly. If the top level then handler returns without re-throwing the error, the following catch handler is not called.

E.G.

fetch('path/to/data.json')
  .then((response) => response.json())
  .then((data) => {
    console.log(`Hey cool, we're done w/ the promise chain here`);
    try {
          startApp(data);
    }
    catch( err) {
      console.log( "An application error occurred:");
      console.error( err);
    }
    // returning without re-throwing the error
  })
 .catch((error) => console.error(`Error in promise chain: ${error}`));

I have also seen timer calls used to throw errors outside the promise chain to prevent them being consumed as promise errors. Not sure if stack tracing is adequate, but at least it pinpoints that an application error occured:

E.G.

fetch('path/to/data.json')
  .then((response) => response.json())
  .then((data) => {
    console.log(`Hey cool, we're done w/ the promise chain here`);
    try {
      startApp(data);
    }
    catch( err) {
        setTimeout( function () { throw err}, 0);
    }
})
.catch((error) => console.error(`Error in promise chain: ${error}`));

The other way is to start the application with a timer so it doesn't execute in the promise chain to start with :-)

setTimeout(startApp, 0, data);
traktor
  • 17,588
  • 4
  • 32
  • 53
  • I'm less interested in specifically catching errors, just that 'wrong' feeling of something I think should come _after_ the promise chain still living inside it. It has the feeling of putting try/catch around giant portions of code. – Scott Thiessen May 12 '18 at 03:13
  • setTimeout actually does the job, jumping the call stack—but for some reason that feels _more_ wrong hah – Scott Thiessen May 12 '18 at 03:17
  • 1
    `setTimeout` is fine if it's fit for purpose. Note `setTimout` doesn't "jump the call stack" since both promise and timer call backs execute with a fresh stack. The big difference is that execution of promise call backs is monitored by the executor function of the promise returned by a then/catch calll, to see if the promise should be rejected if the handler throws an error - while timer callbacks simply report uncaught errors on the console as "uncaught exceptions". – traktor May 12 '18 at 05:05
1

You will always be in the promise chain. However you may easily separate the logic to handle the errors of fetch and your data consuming App as follows:

fetch('path/to/data.json')
  .then(response => response.json())
  .then(startApp, handleFetchError)
  .catch(error => console.error(`Error from startApp or handleFetchError: ${error}`));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Redu
  • 25,060
  • 6
  • 56
  • 76