SpoonX/aurelia-api

Fetch error message discarded

Closed this issue · 26 comments

I've been trying to receive error message without success.
I believe it's because of those lines in rest.js:

    return this.client.fetch(path, requestOptions).then(response => {
      if (response.status >= 200 && response.status < 400) {
        return response.json().catch(error => null);
      }

      throw response;
    });

specifically: response.json().catch(error => null);
the discarded error contains the actual data passed with the error.
but instead the response is thrown: throw response;.
when trying to read from response again "Already read" error is thrown.

In many cases the status error code is not sufficient and the custom error message contains vital information.

Would you consider attaching error to the response before throwing, or giving some API for retrieving the error.

Thanks in advance

hmm. don.t quite get it.
that line only catches errors in the conversion to json. in that case it throws the whole response and you can check why that part failed.
if the server returns an error in the status, as well the whole response is thrown.
that leaves the case, that there is an error in the server, but the server returns status >=200 <400. in that case, the error message, usually is returned as json and you can check that. i don.t think that there are other server settings.
so, i don.t understand which scenario you are refering to.

Thank you for your response.
You are right about the line, my bad.

I'm still unable to read error message.
The scenario is server returning error status of > 400. in which case the response is thrown. I catch it in my then().catch() statement.
How can I obtain the json with all the fields the error contains?
I can see Network debug that message property of the error is sent by the server along side the status. The Response object clearly doesn't contain it.
If I try to call json() on the response I catch, "Already read" type error is thrown.

so, .catch(err=>console.err(err) ) doesn.t contain enough info?

No, it contains the standard Response object. Something like:

//response: Response
{
  body: (...)
  bodyUsed: false
  headers: Headers
  ok: false
  status: 401
  statusText: "Unauthorized"
  type: "cors"
  url: "http://127.0.0.1:8000/api/auth/login"
}
// response.body: ReadableByteStream

While the error response is json object:

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Password mismatch"    // This is the part I'm having trouble to obtain
}

yea, 401 is per default thrown already in aurelia-fetch-client. so try

.catch(response=>response.json())
.then(err=>{console.err(err)} )

Does the trick, thanks!

Now I understand why it didn't work for me earlier and I got the "Already read" type error. Needed to make sure it was > 400 Http error before theresponse.json() call.

@doktordirk

can we please update the code so that underlying code throws the server response object instead of generic fetch api response? Otherwise, as an api consumer, you are force to do this everywhere:

.catch(error=>error.json())
.then(serverError=>{console.err(serverError)} )

Maybe a configuration option to switch it on/off

it doesn't solve the use case when you need to use information in server response to drive certain logic and/or error messages

@msalman86 Doesn't that call for an interceptor more than a library update? 401 simply is an error, that's usually treated as an error. Which usually, even is a generic error (no access, not found, server error); in which case you'd want to set up a generic catch to handle all of those. In exception cases, you can use catch to handle the response.

I might be wrong, and if so, I'm not sure I understand the question. Does this help?

Interceptor is a good backup option. However, it would be nice if the library gives the option to handle error payload that comes from the server.

How can I add an interceptor to aurelia-api plugin that will help me not to repeat .catch().then everywhere? Could you please help? Thank you!

@msalman86 I don't think we can do that. If you need the error, you can take it from the response. In some cases you do want the entire response object. We'd be breaking the implementation of other users. It is an idea to look at the way we handle those types of exceptions through a config I guess. We can offer the option to return the response body instead of the entire response object.

As to interceptors, they're part of the fetch client :) https://github.com/aurelia/fetch-client/blob/master/doc/article/en-US/http-services.md#configuration

@RWOverdijk Doesn't having client side developers referencing the underlying fetch client constitute a leaky abstraction?

@doktordirk Compared to @msalman86 I don't get back a response. The err variable in my catch-block is of type TypeError and therefor calling .json() fails, meaning that .catch(error=>error.json()) .then(serverError=>{console.err(serverError)} doesn't work in my case.

@norgie Honestly, I haven't used interceptors with api before, except with aurelia-authentication. It's not a secret that this library uses fetch client. Configuring the client with your own desires is a normal thing to do I'd say. There's no point in re-implementing interceptors if it's already possible to add them.

As to your error, I'm not sure what you're getting. What is the server returning and why? It might be time to document whatever is the issue here :)

@RWOverdijk Looks like "one man's leaky abstraction is another man's pattern" :-)

As for what is going on, I've already posted a question at SpoonX/Dev on Gitter, but to recap this is what Chrome reports

General:
Request URL:http://localhost:50028/api/stationsfor/customers/160
Request Method:GET
Status Code:404 Not Found
Remote Address:[::1]:50028

Response headers:
HTTP/1.1 404 Not Found
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 06 Mar 2017 15:15:33 GMT

Response:
{"code":10,"details":"Fant ikke det du etterspurte"}

However, in the catch-block on the client I can't find a way to access the content of the response. All I get is an error object with an Aurelia specific stack trace e.g.

GET http://localhost:50028/api/stationsfor/customers/160 404 (Not Found)
:9000/#/stasjonstegninger:1 Fetch API cannot load http://localhost:50028/api/stationsfor/customers/160. No 'Access-Control-Allow

As I said on Gitter this has nothing to do with CORS. I get a TypeError back (in Aurelia) and the json in the response above is lost.

@RWOverdijk
I wonder if the whole problem boils down to a versioning problem. Looking at the package.json file for aurelia-api I notice the following:

"_id": "aurelia-api@3.0.0-rc8"

and under dependencies I find:

"aurelia-fetch-client": "^1.0.0-rc.1.0.0",

@norgie Why are you on rc-8?
We're on 3.1.1

@RWOverdijk
Will update. Someday. ;-)

Joke aside, is this just a case of npm update -g and npm update?

or are there any caveats I should be aware of?

@RWOverdijk

Updated. Same result. All I get back is a type error.

@norgie I get it now.

The weird thing to me is that anyone wants the content on a 404. In any case, is this a problem with aurelia-api or with aurelia-fetch-client?

@RWOverdijk
It is part of a general error handling strategy server side. There are various ways of achieving this, but those I've looked at generates a response containing the http status code as well as more informative information in the response body, e.g. text explaining the cause of the problem and links to help pages if any, etc. Now, perhaps the 404 was a bad example but it was the easiest to recreate as all that is required to get it to fire is to change the url used by the respective find function to some non-existing url.

I have not been able to figure out where the problem lies.

@norgie I'm just super confused. https://github.com/SpoonX/aurelia-api/blob/master/src/rest.js#L85 it throws the response.

Are you sure it's not just something you're causing yourself? If it's a TypeError, could you dump the full error?

@RWOverdijk
This is the call to aurelia-api with the erroneous url-fragment:

	getStationsForCustomer(customerId) {
		return this.endPoint.find("stationsfor/customer/s" + customerId);
	}

This is the code calling the above function:

	attached() {
		return this.stationDrawingsService
		           .getStationsForCustomer(this.selectedCustomer.id)
		           .then(res => this.stations = res.stations);
	}

This is the stack trace:

GET http://localhost:50028/api/stationsfor/customer/sBlah-Blah 404 (Not Found)
:9000/#/stasjonstegninger:1 Fetch API cannot load http://localhost:50028/api/stationsfor/customer/sBlah-Blah. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access. The response had HTTP status code 404. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
vendor-bundle.js:1335 Unhandled rejection TypeError: Failed to fetch
    at applyInterceptors (http://localhost:9000/scripts/vendor-bundle.js:22847:16)
    at processResponse (http://localhost:9000/scripts/vendor-bundle.js:22830:12)
    at http://localhost:9000/scripts/vendor-bundle.js:22745:18
From previous event:
    at http://localhost:9000/scripts/vendor-bundle.js:22744:24
From previous event:
    at HttpClient.<anonymous> (http://localhost:9000/scripts/vendor-bundle.js:22732:64)
    at HttpClient.fetch (http://localhost:9000/scripts/vendor-bundle.js:22716:23)
    at Rest.request (http://localhost:9000/scripts/vendor-bundle.js:22911:26)
    at Rest.find (http://localhost:9000/scripts/vendor-bundle.js:22923:19)
    at StationDrawingsService.getStationsForCustomer (http://localhost:9000/scripts/app-bundle.js:1282:25)
    at StationDrawings.attached (http://localhost:9000/scripts/app-bundle.js:7937:39)
    at Controller.attached (http://localhost:9000/scripts/vendor-bundle.js:19780:24)
    at View.attached (http://localhost:9000/scripts/vendor-bundle.js:17867:25)
    at View._aureliaTemplating.View.attached (http://localhost:9000/scripts/vendor-bundle.js:66445:16)
    at ViewSlot.add (http://localhost:9000/scripts/vendor-bundle.js:18035:14)
    at http://localhost:9000/scripts/vendor-bundle.js:22121:29
From previous event:
    at http://localhost:9000/scripts/vendor-bundle.js:22119:30
From previous event:
    at after (http://localhost:9000/scripts/vendor-bundle.js:22004:56)
    at work (http://localhost:9000/scripts/vendor-bundle.js:22103:9)
    at http://localhost:9000/scripts/vendor-bundle.js:22133:18
From previous event:
    at RouterView.swap (http://localhost:9000/scripts/vendor-bundle.js:22131:87)
    at http://localhost:9000/scripts/vendor-bundle.js:14700:29
    at Array.forEach (native)
    at http://localhost:9000/scripts/vendor-bundle.js:14699:20
From previous event:
    at NavigationInstruction._commitChanges (http://localhost:9000/scripts/vendor-bundle.js:14698:33)
    at CommitChangesStep.run (http://localhost:9000/scripts/vendor-bundle.js:14547:36)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at iterate (http://localhost:9000/scripts/vendor-bundle.js:15601:14)
    at processActivatable (http://localhost:9000/scripts/vendor-bundle.js:15604:12)
    at ActivateNextStep.run (http://localhost:9000/scripts/vendor-bundle.js:15485:14)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at iterate (http://localhost:9000/scripts/vendor-bundle.js:15514:14)
    at processDeactivatable (http://localhost:9000/scripts/vendor-bundle.js:15517:12)
    at DeactivatePreviousStep.run (http://localhost:9000/scripts/vendor-bundle.js:15473:14)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at iterate (http://localhost:9000/scripts/vendor-bundle.js:15601:14)
    at processActivatable (http://localhost:9000/scripts/vendor-bundle.js:15604:12)
    at CanActivateNextStep.run (http://localhost:9000/scripts/vendor-bundle.js:15461:14)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at AuthenticateStep.run (http://localhost:9000/scripts/vendor-bundle.js:24295:14)
From previous event:
    at LoadRouteStep.run (http://localhost:9000/scripts/vendor-bundle.js:15748:68)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at iterate (http://localhost:9000/scripts/vendor-bundle.js:15514:14)
    at processDeactivatable (http://localhost:9000/scripts/vendor-bundle.js:15517:12)
    at CanDeactivatePreviousStep.run (http://localhost:9000/scripts/vendor-bundle.js:15449:14)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at http://localhost:9000/scripts/vendor-bundle.js:14952:16
From previous event:
    at BuildNavigationPlanStep.run (http://localhost:9000/scripts/vendor-bundle.js:14950:58)
    at next (http://localhost:9000/scripts/vendor-bundle.js:14516:20)
    at Pipeline.run (http://localhost:9000/scripts/vendor-bundle.js:14529:14)
    at http://localhost:9000/scripts/vendor-bundle.js:16077:25
From previous event:
    at AppRouter._dequeueInstruction (http://localhost:9000/scripts/vendor-bundle.js:16050:32)
    at http://localhost:9000/scripts/vendor-bundle.js:16041:17
From previous event:
    at AppRouter._queueInstruction (http://localhost:9000/scripts/vendor-bundle.js:16038:14)
    at http://localhost:9000/scripts/vendor-bundle.js:15972:23
From previous event:
    at AppRouter.loadUrl (http://localhost:9000/scripts/vendor-bundle.js:15971:53)
    at BrowserHistory._loadUrl (http://localhost:9000/scripts/vendor-bundle.js:11154:55)
    at BrowserHistory._checkUrl (http://localhost:9000/scripts/vendor-bundle.js:11147:14)
printWarning @ vendor-bundle.js:1335
formatAndLogError @ vendor-bundle.js:1051
fireRejectionEvent @ vendor-bundle.js:1076
Promise._notifyUnhandledRejection @ vendor-bundle.js:581
(anonymous) @ vendor-bundle.js:132

Right, but that is cors. That error ^ is a cors error. Perhaps your 404 response isn't configured properly?

@RWOverdijk
Sounds plausible. Our back-end has a general allow CORS set-up, but as you say it may not trigger when errors like 404 or 500 are thrown. I'll modify the back-end and come back to you with more info. Thx for being both patient and helpful.

The only issue I have with that hypothesis is that there's nothing in the response as reported by Chrome's dev.tool that says anything about this being caused by CORS.

Request URL:http://localhost:50028/api/stationsfor/customer/sBlah-Blah
Request Method:GET
Status Code:404 Not Found
Remote Address:[::1]:50028
Response Headers
view parsed
HTTP/1.1 404 Not Found
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 08 Mar 2017 13:08:53 GMT

The options call preceding the above GET returns 200 OK including:

Access-Control-Allow-Headers:authorization, content-type
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS

But as I said above, I'll modify the back-end and see what happens.

@RWOverdijk

Of course it was a CORS error.

Man, you never cease to impress me.

Thx for all your help. It is highly appreciated.