I use Aurelia Fetch Client library to fetch JSON data from the backend server by the code:
getData() {
let httpClient = new HttpClient();
return httpClient.fetch('http://localhost:9220/get-data')
.then(response => response.json())
.then(data => return data);
}
}
And the metod getData() is called from the another code by the code:
dataService.getData().then(data => {
this.data = data;
}).catch(error => {
this.backendError = true;
});
As you can see I use here a catch statement and in case of error it's called, but I also see in the console an error message that comes from the library: "vendor-bundle.js:1395 Unhandled rejection TypeError: Failed to fetch". How can I get rid it?
I'm unsure if this is a bug with the Aurelia HTTP Fetch Client, but adding a responseError interceptor should remove the Unhandled Exception warning in the console.
let http = new HttpClient();
http.configure(config => {
config.withInterceptor({
response(response) {
return response;
},
responseError(error) {
return error;
}
})
});
This error may also come from the UseDeveloperExceptionPage middleware in a .NET Core API. This middleware strips all headers from the response which create CORS issues and causes the "TypeError: Failed to fetch" error you saw. Here is an example of my solution, which is described in full here.
.NET Core Middleware
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { error = "An internal server error has occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
Aurelia Interceptor
responseError(response: any): Promise<Response> {
if (response instanceof Response) {
return response.json().then((serverError: ServerError) => {
// Do something with the error here.
return Promise.reject<Response>(serverError.error);
});
}
}
Related
I've already read tons of resources to try to help me on this. This gist did not solve it for me (https://github.com/github/fetch/issues/203#issuecomment-266034180). It also seemed like this (JavaScript Promises - reject vs. throw) would be my answer but it is not. Also this (Error thrown in awaited Promise not caught in catch block) and this (errors not being thrown after promise).
I'm developing a project using a Yii2 PHP server-side solution, and Vue frontend solution. The project has several resources (lessons, media, etc) and REST API endpoints on the server-side that all are used the same. My dev work would benefit from me creating a re-usable API client class (in native JS - not anyting Vue related). I created an 'abstract' class that I 'extend' for each resource and use its functions for the CRUD operations.
I'd like to set up some middleware functions that are going to process the response from the API so that will be handled in the same fashion after every request I make so that I don't have to reproduce that processing code in the Vue apps and components that are using those API client classes.
The code is using the native JS fetch() function. I'm using .then() and .catch() in the functions as needed to process responses and control the flow.
My problem is that I have a function to process the API response, and in it I throw an error if I receive a non-200 response. I've implemented .catch() blocks in several places but I always get an error "Uncaught (in promise)" regardless of putting catch() calls everywhere.
When a user starts watching a video, I make an API call to my server to update a status on a user_media record. So, in the Vue component, I use my UserMedia helper class to create() a resource on the server and implement a then() and catch() on that. When there is an error server-side, I expect the catch() to catch that error and handle it. But, I just get the error "Uncaught (in promise)" as if I'm not trying to catch the error at all.
In the code, I am using updateWatchedStatus() in the vimeo video component, that calls the UserMediaApi.create() which calls YiiApiHelper.request() which calls YiiApiHelper.processRestResponse() where the error is thrown. I've tried implementing catch() blocks all over the place but it's never caught.
CLEARLY, I don't understand something about either fetch(), promises, or catching errors. But I can't figure it out. It seems like the only way around this is to have to write a bunch more code to try to compensate. Any help is appreciated. Even if I'm going about this all wrong and should be doing it someway else entirely.
The full code for that can be seen here:
YiiApiHelper.js https://pastebin.com/HJNWYQXg
UserMediaApi.js https://pastebin.com/9u8jkcSP
Vimeo Video Vue Component https://pastebin.com/4dJ1TtdM
For brevity, here's what's important:
Generic API Helper:
const request = function(resource, options){
return fetch(resource, options)
.then(response => Promise.all([response, response.json()]));
}
const resourceUrl = function(){
return this.autoPluralizeResource ?
this.resourceName+'s' :
this.resourceName;
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options)
.then(this.processRestResponse);
}
const processRestResponse = function([response, body]){
if(!response.ok){
if(response.status == 422){
if(Array.isArray(body)){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
throw {
name: response.status,
message: messages.join("\n")
}
}
}
throw {
name: response.status,
message: (body.message) ?
body.message :
response.statusText
}
}
return Promise.all([response, body]);
}
export default {
baseUrl: '',
resourceName: '',
autoPluralizeResource: true,
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
UserMedia helper:
import YiiApiHelper from './../../yiivue/YiiApiHelper.js';
export default {
...YiiApiHelper,
baseUrl: window.location.origin+'/media/api/v1',
resourceName: 'user-media',
autoPluralizeResource: false
}
VimeoVideo.js:
let updateWatchedStatus = function(watchedStatusId) {
if(!props.userMedia){
// --- User has no record for this media, create one
return UserMediaApi.create({
media_id: props.media.id,
user_id: props.userId,
data: {
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
// --- User has a record, update the watched status in the data
let data = {
...userMedia.value.data,
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
return UserMediaApi.update(props.media.id+','+props.userId, {
data: data
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
Figured out and fixed this a while ago and figured I should come back in case it helps anyone.
Wrapping the request in a promise, and passing its resolve/reject into promises returned was the solution.
The code below isn't complete but it's enough to illustrate what had to be done to get this working as intended:
const request = function(resource, options){
return new Promise((resolve, reject) => {
return fetch(resource, options)
.then(response => {
if(
options &&
options.method == "DELETE" &&
response.status == 204
){
// --- Yii2 will return a 204 response on successful deletes and
// --- running response.json() on that will result in an error
// --- "SyntaxError: Unexpected end of JSON input" so we will just
// --- avoid that by returning an empty object
return Promise.all([response, JSON.stringify("{}"), resolve, reject])
}
// --- Include resolve/reject for proper error handling by response processing
return Promise.all([response, response.json(), resolve, reject])
}).then(this.processRestResponse)
});
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options);
}
const processRestResponse = function([response, body, resolve, reject]){
// --- If the response is okay pass it all through to the function
// --- that will be handling a response
if(response.ok){
return resolve([response, body]);
}
// --- If there are validation errors prepare them in a string
// --- to throw a user friendly validation error message
if(
response.status == 422 &&
Array.isArray(body)
){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
return reject({
name: response.status,
message: messages.join("\n")
})
}
// --- If there is another error just provide the status text
// --- as a message (Yii provides this)
return reject({
name: response.status,
message: (body.message) ?
body.message :
response.statusText
})
}
export default {
baseUrl: '',
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
I have an asp.net core MVC server set up, I am trying to enable some better error handling. However when I deploy my changes to production environment I have a discrepancy in my server responses when dealing with 4xx errors.
When running on my local host i am able to send custom response data back to the client and read this data no problem, however when i attempt the same thing after live deployment I cannot read the responses the same way And I do not understand why.
Controller
[HttpPost]
public JsonResult SaveRecord([FromBody]NewsLanguage newLanguage)
{
//NewsLanguage newLanguage = new NewsLanguage()
try
{
_context.NewsLanguages.Add(newLanguage);
_context.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Response.StatusCode = 409;
string errMsg = ex.Message;
if (ex.InnerException != null)
errMsg = ex.InnerException.Message;
return Json(new { status = "Error", message = errMsg });
}
Response.StatusCode = 200;
return Json(new { status = "success",
message = "New News Language Saved Successfully!" });
}
fetch request
try {
const response = await submitForm("/saverecord", newsLanguage, "POST");
console.log(response);
if (response.ok)
handleResponse(response, newsLanguage);
else {
const err = await response.json();
throw new Error(err.message || err.statusText)
}
} catch (err) {
console.log(err);
handleErrorResponse(err, newsLanguage);
}
function submitForm(route, newsLanguage, method) {
const requestOptions =
{
method: method,
headers:
{
'Content-Type': 'application/json'
},
body: JSON.stringify(newsLanguage)
};
return fetch(parurl + route, requestOptions);
}
async function handleResponse(response, newsLanguage, method) {
const data = await response.json();
console.log(response, data)
if (data.status === "success") {
//have to close modal this way since using
//jquery hide leave backdrop open and causes
//issue with subsequent modal openings
document.getElementById("ModalFormClose").click();
toastr.success(data.message, "PERLEWEB DATABASE INTERFACE");
if (method != "DELETE") {
let table = $('#example').DataTable();
table.row.add({ "id": newsLanguage.Id,
"languageName": newsLanguage.LanguageName }).draw();
} else {
var table = $('#example').DataTable();
table.row($(this).parents('tr')).remove().draw();
}
} else {
toastr.error(response.responseJSON.message, "ERROR!")
}
}
function handleErrorResponse(errorMsg) {
toastr.error(errorMsg, "ERROR!")
}
So it seems the custom error message i send when sending the 409 response is not there in the client in production, however, the success message is i can read it as expected and display the message, however when trying to read the response.json() after checking if response is ok (and when it is not) the response message is "SyntaxError: Unexpected token T in JSON at position 0" which based on some other research suggest it is undefined.
So my main questions are,
1- where is my error message for failures?
2- Is there a way i can get it display the error message, or can i only send http response code for error?
3- why does it work for success response but not error?
4- why is there this difference btwn localhost vs production is this a server configuration issue?
Thanks
After Much investigation, it turns out the source of the issue was in the web.configurations.
Since the project is being inside of another web app i had to add a section to my web.config which specifies a different custom error handling method than the rest of the site. specifically i added the below
<location path="webdb">
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough" >
<clear/>
</httpErrors>
</system.webServer>
</location>
And Now i am able to parse the Error response custom text in my JS and display the message from the server
I use the axios interceptors to handle some errors, specially the errors without response. And in some parts of my project, I use the message contained in the error.response.data for validations and showing a messaged stored in the backend. But this interceptor is not preventing me from having to check if the error has a response.
My interceptor:
axios.interceptors.response.use(
function (response) {
...
},
function (error) {
if (!error.response) {
...
return Promise.reject(new Error(error.message))
}
An example of a request that depends on having the error.response:
this.$store.dispatch('updateField', { [this.fieldKey]: this.value ? this.value : null }).catch((error) => {
this.validateField(error.response.data)
})
But I'd have to put the validateField call inside an if(eror.response) to avoid an error in the console, and spread this if all over my code?
response can be treated as optional, because it actually is:
this.validateField(error.response?.data)
Or normalized error that contains needed properties and doesn't rely on the structure of Axios error can be created in an interceptor:
function (rawError) {
const error = new Error(error.response?.data?.myError || error.message);
error.data = error.response?.data || null;
error.headers = error.response?.headers || null;
error.status = error.response?.status || 0;
return Promise.reject(error);
}
Basically, the weather API; Apixu changed everything to weatherstack recently, including their endpoints and I need help updating my twitter weather bot.
I did go through the documentation, changed to axios but I keep getting the "Cannot Read Property error"
My Old API Setup
const Twit = require('twit');
const config = require('./config');
const rp = require('request-promise-native');
async function setup(location) {
const options = {
url: "http://api.apixu.com/v1/current.json",
qs: {
key: API_KEY,
q: location
},
json: true
};
let result = await rp(options);
let condition = result.current.condition.text;
let tweetText = `The condition in ${location} is currently ${condition}!`;
console.log("TWEETING : ", tweetText);
sendTweet(tweetText)
}
According to their documentation, this is how it's supposed to be but I keep getting undefined errors.
const params = {
access_key: 'YOUR_ACCESS_KEY',
query: 'New York'
}
axios.get('https://api.weatherstack.com/current', {params})
.then(response => {
const apiResponse = response.data;
console.log(`Current temperature in ${apiResponse.location.name} is ${apiResponse.current.temperature}℃`);
}).catch(error => {
console.log(error);
});
The new Base URL: The new API requests start out with :
http://api.weatherstack.com/
documentation : https://weatherstack.com/quickstart
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'c
ondition' of undefined
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function without a catch
block, or by rejecting a promise which was not handled with .catch(). (rejection
id: 1)
I would check the response.data.error object, if something goes wrong this will be populated. Funnily enough the http status code is still 200 for some error conditions.
axios.get('https://api.weatherstack.com/current', {params})
.then(response => {
if (!response.data.error) {
const apiResponse = response.data;
console.log(`Current temperature in ${apiResponse.location.name} is ${apiResponse.current.temperature}℃`);
} else {
console.log(`Response error: code: ${response.data.error.code}, info: ${response.data.error.info}`)
}
}).catch(error => {
console.error("An error occurred: ", error);
}
);
Using the free tier, I'm getting the following error with this request:
Response error: code: 105, info: Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.
This is easily worked around by changing to http only (This will be less secure!):
axios.get('http://api.weatherstack.com/current', {params})
.then(response => {
if (!response.data.error) {
const apiResponse = response.data;
console.log(`Current temperature in ${apiResponse.location.name} is ${apiResponse.current.temperature}℃`);
} else {
console.log(`Response error: code: ${response.data.error.code}, info: ${response.data.error.info}`)
}
}).catch(error => {
console.error("An error occurred: ", error);
}
);
If you are using free version you need to use 'http' to work, i guess if you want to use 'https' it is premiun that you need to buy
Here is the simple example that i have used
http://api.weatherstack.com/current?access_key=0a82bdc4c6628b5f968dd500d30a8857&query=19.0760,-72.8777
We're working with Angular 5 and a Spring 2 OAuth Backend.
Now when I send an old token it's of course expired. It returns status code: 401 and an error response with invalid token and so on. Now I can't see it in my logs or when I catch the error. I want to get the error so I can at first log it and later on either refresh the token or send him to the Login Page.
Now if i subscribe to the request with:
.subscribe(res => {
//just random stuff.
}, err => {
console.log("error", err);
});
I just see this response in the log with an unknown error like in this image
Could it be failure of the backend? Because i also see in the logs something like a "No 'Access-Control-Allow-Origin' header is present"-error, although it's because of the invalid token.
Although I can see this response code in Google Chrome Dev Tools
and a 401 status code.
So I tried to find a solution myself. I've already got an interceptor and tried it with some solutions
return next.handle(authReq)
.catch(error => {
console.log("im in here");
console.log(error);
return Observable.throw(error);
});
The Http Service just throws an error that catch is not a function without even logging the error or the "im in here".
I have also tried with the .do after next.handle and I got the same error like catch
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}, (err: any) => {
console.log(err);
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
}
}
});
I've tried with pipe after the http.get but it doesn't work either.
http.get(...).pipe(
retry(3), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error
);
import 'rxjs/add/operator/catch';
Somefunc(){
this.httpClient
.get("data-url")
.subscribe(
data => console.log('success', data),
error => console.log('oops', error)
);
}
OR
this.httpClient
.get("data-url")
.catch((err: HttpErrorResponse) => {
// simple logging, but you can do a lot more, see below
console.error('An error occurred:', err.error);
});
Should work.