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