16

I have an API that includes a useful description of what went wrong when an error is raised by the server (status = 500). The description comes as part of the response text. My client code, using Aurelia, calls the api via aurelia-fetch-client using a generic method to make the call:

function callRemoteService(apiName, timeout) {
  return Promise.race([
    this.http.fetch(apiName),
    this.waitForServer(timeout || 5000)  // throws after x ms
  ])
    .then(response => response.json() )
    .catch(err => {
        if (err instanceof Response) {
          // HERE'S THE PROBLEM.....
          err.text().then(text => {
            console.log('Error text from callRemoteService() error handler: ' + text);
            throw new Error(text)
          });
        } else if (err instanceof Error) {
          throw new Error(err.message);
        } else {
          throw new Error('Unknown error encountered from callRemoteService()');
        }
    });
}

Note that I want to catch the server (fetch or timeout) errors in a consistent way, and then throw back just a simple error message to the calling view. I can invoke callRemoteService successfully, catching errors when a 500 is returned with:

callRemoteService(this.apiName, this.apiTimeout)
  .then(data => {
    console.log('Successfully called \'' + this.apiName +
      '\'! Result is:\n' + JSON.stringify(data, null, 2));
    })
  .catch(err => {
    console.log('Error from \'' + this.apiName + '\':',err)
    });

However, I'm having trouble accessing the response text, because the fetch provides the text() method that returns a promise, and that's interfering with my otherwise happy promise chaining. The code above doesn't work, leaving me with an Uncaught (in promise) error.

Hopefully there's a good way to access that response text?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
AR.
  • 39,615
  • 9
  • 44
  • 52
  • does putting the `catch` before the `then` (on the top code) help? – dandavis Feb 20 '16 at 08:54
  • @dandavis I've tried several variations without luck. Jeremy's suggestion of keeping the promise chain inside the `race()` seems to work, however. – AR. Feb 20 '16 at 21:58

1 Answers1

12

This should do the trick:

function callRemoteService(apiName, timeout = 5000) {
  return Promise.race([
    this.http.fetch(apiName)
      .then(
        r => r.json(),
        r => r.text().then(text => throw new Error(text))
      ),
    this.waitForServer(timeout)
  ]);
}

by the way, I like what you're doing with Promise.race- nice technique!

Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • 2
    But it leaves the request open and pending, right? Is that good? – Matthew James Davis Feb 20 '16 at 21:17
  • 2
    oh- I see- in the event of a timeout, the request is still pending. Not good but no answer for that until they sort things out in the spec. https://github.com/whatwg/fetch/issues/20 https://github.com/whatwg/fetch/issues/27 – Jeremy Danyow Feb 20 '16 at 21:49
  • 1
    This got me most of the way, thanks. Your suggestion of keeping the promise chain inside the `race()` was the key. However, as written your solution still only returns a promise to the caller, not the actual response text. The following adjustment work for me: `this.http.fetch(apiName).then(r => r.json(), r => r.text().then(text => { throw new Error(text) } ))` If you update your answer I'll go ahead and accept. Thanks again for your help! – AR. Feb 20 '16 at 21:55