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: '',
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);
},
}
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./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 ponent 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 ponent?
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 ponent, 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 ponent:
const token = '*********************';
const axiosOptions = {
baseURL: 'https://www.udemy./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 ponent 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
Share
Improve this question
edited Feb 6, 2019 at 13:33
Andrew Dunn
asked Feb 5, 2019 at 18:31
Andrew DunnAndrew Dunn
7551 gold badge7 silver badges21 bronze badges
13
-
Please do a
console.log(error)
and what message you are getting. – Bergi Commented Feb 5, 2019 at 18:54 -
3
Due to the wrong
.catch(error => error);
I doubt that yourcatch
statement actually gets called at all. And if it is, that's likely because of an exception in the precedingthen
callback – Bergi Commented Feb 5, 2019 at 18:56 -
1
The
.then(response => response.data)
is totally fine, you should keep it if you want it. – Bergi Commented Feb 6, 2019 at 13:27 -
1
Axios throws a
Error: "Network Error"
though, and the request hasstatus = 0
not 404. Maybe a CORS problem, and you're not allowed to access the response? – Bergi Commented Feb 6, 2019 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 Commented Feb 6, 2019 at 13:53
2 Answers
Reset to default 6So, 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.
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