4

I am currently working on a project to create a client for the Udemy instructor API.

I've written the client in Vue, using Axios as my HTTP client.

I have abstracted the different API requests into functions in an ES6-ified API wrapper library (Udemy.js) to allow them to be easily reused.

Udemy.js first initialises an instance of Axios, then exports API functions that use that instance as a base as promises.

Below is taken from the file, though I have removed all but one of the functions the module exports for ease of reading (and obviously redacted the API token). The endpoint URI contains "message-threadssss" — this is deliberate, to cause the server to return 404:

import axios from 'axios';

const token = '***************************';
const axiosOptions = {
  baseURL: 'https://www.udemy.com/instructor-api/v1',
  timeout: 10000,
  headers: {
    Accept: '*/*',
    'Content-Type': 'application/json;charset=utf-8',
    Authorization: `Bearer ${token}`,
  },
};

const axiosInstance = axios.create(axiosOptions);

export default {
  postMessage(messageThreadId, messageBody) {
    return axiosInstance
            .post(`/message-threadssss/${messageThreadId}/messages/`, {
              content: messageBody,
            })
            .then(response => response.data)
            .catch(error => error);
  },
}

UdemyApi.postMessage(threadId, threadReply);
.then((response) => {
        this.isLoading = false;
        this.sentReply = response;
        this.replyBody = '';
        this.$root.$emit('reply-sent', {
          threadId: this.thread.id,
          sentReply: this.sentReply,
        });
      })
      .catch((error) => {
        if (error.response) {
          // Case 1 (Server returned error)
          console.log(error.response.data);
          console.log(error.response.status);
          console.log(error.response.headers);
        } else if (error.request) {
          // Case 2 (Pre-response error)
          console.log(error.request);
        } else {
          // Case 3 (Mysterious error)
          console.log('Error:', error.message);
        }
        this.$root.$emit('show-snackbar', {
          message: `Failed to send. ${error} `,
          actionText: 'Understood. :(',
        });
        this.isLoading = false;
      });

The request sends without a problem, and if the request is a success (i.e. 2xx), the Vue component is able to access the response data in the then() block.

When the server returns an error (404 in this instance), I would expect the caught error to contain a response object (Case 1).

Instead though, no response object is returned with the error (Case 2), which prevents me from handling it correctly. This happens when the request does cause the server to respond with a 404 error:

HTTP/2.0 404 Not Found
content-type: text/json

I've read that if Axios has interceptors applied to it, that can lead to this issue, but in this case, I've not applied any interceptors.

All in all, I'm at a bit of a loss. How do I get the server's response into my Vue component?

Edit (6th Feb)

I didn't include the all-useful console output in my initial post, so here it is. The console.log() line executed is the Case 2 line (just one console log entry is added, not prefixed with "Error: ", as would be the case in Case 3):

12:22:28.748
XMLHttpRequest
    mozAnon: false
    mozSystem: false
    onabort: null
    onerror: function handleError()
    onload: null
    onloadend: null
    onloadstart: null
    onprogress: null
    onreadystatechange: function handleLoad()
    ontimeout: function handleTimeout()
    readyState: 4
    response: ""
    responseText: ""
    responseType: ""
    responseURL: ""
    responseXML: null
    status: 0
    statusText: ""
    timeout: 100000
    upload: XMLHttpRequestUpload { onloadstart: null, onprogress: null, onabort: null, … }
    withCredentials: false
    <prototype>: XMLHttpRequestPrototype { open: open(), setRequestHeader: setRequestHeader(), send: send(), … }
replybox.vue:72

Edit 2 (6th Feb)

If I remove the then() and catch() from the postMessage() definition to look like this:

 postMessage(messageThreadId, messageBody) {
    return axiosInstance
            .post(`/message-threadssss/${messageThreadId}/messages/`, {
              content: messageBody,
            });
  },

And then simplify the catch() block of the postMessage() call to just output the error object to look like this:

    .catch((error) => {
        console.log(error);
        this.$root.$emit('show-snackbar', {
          message: `Failed to send. ${error} `,
          actionText: 'Understood. :(',
        });
        this.isLoading = false;
      });

The console outputs:

12:38:51.888 Error: "Network Error"
    createError webpack-internal:///./node_modules/axios/lib/core/createError.js:16:15
    handleError webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:87:14
replybox.vue:62

Edit 3 (6th Jan)

I realised in my previous edit, I omitted the output of error.request after I'd removed .then and .catch from my postMessage definition. If I re-add console.log(error.request); to the .catch block of the call in my component, this is the output:

12:58:55.436
XMLHttpRequest
    mozAnon: false
    mozSystem: false
    onabort: null
    onerror: function handleError()
    onload: null
    onloadend: null
    onloadstart: null
    onprogress: null
    onreadystatechange: function handleLoad()
    ontimeout: function handleTimeout()
    readyState: 4
    response: ""
    responseText: ""
    responseType: ""
    responseURL: ""
    responseXML: null
    status: 0
    statusText: ""
    timeout: 100000
    upload: XMLHttpRequestUpload { onloadstart: null, onprogress: null, onabort: null, … }
    withCredentials: false
    <prototype>: XMLHttpRequestPrototype { open: open(), setRequestHeader: setRequestHeader(), send: send(), … }

Edit 4 (6th Feb)

To confirm or rule out my implementation of my API abstraction layer, I directly invoked an Axios instance in my component:

const token = '*********************';
const axiosOptions = {
  baseURL: 'https://www.udemy.com/instructor-api/v1',
  timeout: 100000,
  headers: {
    Accept: '*/*',
    'Content-Type': 'application/json;charset=utf-8',
    Authorization: `Bearer ${token}`,
  },
};
const axiosInstance = axios.create(axiosOptions);
axiosInstance
.post(`/message-threadssss/${this.thread.id}/messages/`, {
  content: this.replyBody,
})
.then((response) => {
  this.isLoading = false;
  this.sentReply = response;
  this.replyBody = '';
  this.$root.$emit('reply-sent', {
    threadId: this.thread.id,
    sentReply: this.sentReply,
  });
})
.catch((error) => {
  console.log('Error obj: ', error);
  console.log('Request error obj: ', error.request);
  this.$root.$emit('show-snackbar', {
    message: `Failed to send. ${error} `,
    actionText: 'Understood. :(',
  });
  this.isLoading = false;
  this.axiosResult = error;
});

As before, the server returned the expected 404, and the .catch block in my component caught the error.

As before though, the response was missing from the caught error

13:25:45.783 Error obj:  Error: "Network Error"
    createError webpack-internal:///./node_modules/axios/lib/core/createError.js:16:15
    handleError webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:87:14
replybox.vue:79

13:25:45.786 Request error obj:  
XMLHttpRequest
    mozAnon: false
    mozSystem: false
    onabort: null
    onerror: function handleError()
    onload: null
    onloadend: null
    onloadstart: null
    onprogress: null
    onreadystatechange: function handleLoad()
    ontimeout: function handleTimeout()
    readyState: 4
    response: ""
    responseText: ""
    responseType: ""
    responseURL: ""
    responseXML: null
    status: 0
    statusText: ""
    timeout: 100000
    upload: XMLHttpRequestUpload { onloadstart: null, onprogress: null, onabort: null, … }
    withCredentials: false
    <prototype>: XMLHttpRequestPrototype { open: open(), setRequestHeader: setRequestHeader(), send: send(), … }
replybox.vue:80
Andrew Dunn
  • 575
  • 1
  • 6
  • 19
  • Please do a `console.log(error)` and what message you are getting. – Bergi Feb 05 '19 at 18:54
  • 3
    Due to [the wrong `.catch(error => error);`](https://stackoverflow.com/questions/16371129/chained-promises-not-passing-on-rejection) I doubt that your `catch` statement actually gets called at all. And if it is, that's likely because of an exception in the preceding `then` callback – Bergi Feb 05 '19 at 18:56
  • Thanks for commenting @Bergi, and for the link to your enlightening post (I'm still trying to get my head around promises, so I've read it with a scrunched-up face a few times over, and think I understand). I've followed your advice, and updated my question with the console output from my initial call, and further output having removed the `.then` and `.catch` from `postMessage` and `console.log`-ed only the `error` object. The output in both instances though would indicate that the `catch` statement in my component is called. – Andrew Dunn Feb 06 '19 at 12:56
  • 1
    The `.then(response => response.data)` is totally fine, you should keep it if you want it. – Bergi Feb 06 '19 at 13:27
  • After the update, I'm not sure I understand where the problem is. You are getting a pre-response `Network Error`, and it does have a `.request` property with an XHR instance as expected. What do you need help with? – Bergi Feb 06 '19 at 13:33
  • Sorry for the lack of clarity. Essentially, the request does complete, and the server returns a 404 error. I want to catch this error, but cannot because `error.response` is missing. – Andrew Dunn Feb 06 '19 at 13:36
  • 1
    Axios throws a `Error: "Network Error"` though, and the request has `status = 0` not 404. Maybe a CORS problem, and you're not allowed to access the response? – Bergi Feb 06 '19 at 13:40
  • 1
    Well. What a rookie error for me to overlook. It was exactly that. It seems the API only allows cross-origin requests when returning a 2xx status. I had wrong assumed this was a blanket policy. The most embarrassing part is that I'd have seen this if I'd not have filtered my console so I could focus on what I thought to be the relevent output. Thanks so much for your help! – Andrew Dunn Feb 06 '19 at 13:53
  • TBH, I think that API is broken and you should file a bug report for them to send cors headers with their error responses as well. – Bergi Feb 06 '19 at 13:56
  • Agreed. It's fairly new, and I think they still have much to iron out in it. I'll pop up an answer and mark this resolved. Thanks again. – Andrew Dunn Feb 06 '19 at 14:00
  • Does this answer your question? [Determine if ajax call failed due to insecure response or connection refused](https://stackoverflow.com/questions/31058764/determine-if-ajax-call-failed-due-to-insecure-response-or-connection-refused) – ecarrizo May 29 '20 at 15:11
  • Hey @ecarrizo, thanks for your comment. The issue was CORS in this case. I ended up answering my own question. :) – Andrew Dunn May 29 '20 at 16:47
  • I know that you figured out what was causing the communication error but i dont think that it answer the question you posted. The reason why you are not able to access or get errors from the response in that conext is explained in the question link in the flag as duplicate – ecarrizo May 31 '20 at 04:35

2 Answers2

2

I'm pretty sure all you need to do is remove the .then and .catch from your postMessage function and you should be good to go

postMessage(messageThreadId, messageBody) {
  return axiosInstance
     .post(`/message-threadssss/${messageThreadId}/messages/`, {
        content: messageBody,
      })
}

This way, postMessage is returning a promise. And when you actually call postMessage you can use .then and .catch

Isaac Vidrine
  • 1,568
  • 1
  • 8
  • 20
  • Thanks for your answer Isaac! I've updated my `postMessage` function according to your advice. It seems that the issue remains however. I've updated my question with the results of making that change. – Andrew Dunn Feb 06 '19 at 12:59
2

So, the answer it seems was actually the result of the API I was calling not serving the correct CORS headers with error responses (so CORS was only allowed for 2xx responses).

Consequently, Axios was unable to access the response.

I'll need to work around a general ambiguous error for the moment, but solution going forwards lies with developers of the API serving CORS with both success and error responses.

Many thanks to Bergi for their help, which ultimately led me to the cause of the issue.

Andrew Dunn
  • 575
  • 1
  • 6
  • 19