I wrote a login method which sends a http post to an authorization service. If the credentials are wrong, the service returns a 403.
My idea is to return a Promise.reject() and let the caller catch the rejection to show an alert. My problem is, the calling method does not handle the error correctly.
Is there anything I am doing wrong with my error procedure?
public async login(username: string, password: string): Promise<void> {
let authToken = null;
const obj = this;
const url = this.backendService.getAuthServiceURL(username, password);
this.http.post<any>(url, { }, {
headers: new HttpHeaders(),
observe: 'response',
}).subscribe(
(result) => {
authToken = result.headers.get('Authorization');
localStorage.setItem(authTokenIdentifier, authToken);
obj.username = username;
localStorage.setItem(usernameIdentifier, username);
},
(error) => {
if (error.status === 403) {
localStorage.removeItem(authTokenIdentifier);
return Promise.reject(error.message);
} else {
return Promise.reject(error.message);
}
},
() => {
},
);
}
async login(): Promise<void> {
this.authService.login(this.username, this.password)
.then( () => console.log("SUCCESS!"))
.catch(() => console.log("ERROR!"));
}
I already tried wrapping the authService.login call in Try/Catch, calling it with await
await this.authService.login(this.username, this.password)
and not running the code async at all.
Thank you for any advice in advance.
Edit:
If I understand you guys correctly, returning the Error wrapped in a Promise is unnecessary and/or can cause problems. I have to admit I didn't really understand that subscribe will create an Observable.
(error) => {
if (error.status === 403) {
localStorage.removeItem(authTokenIdentifier);
throwError(error.message);
} else {
throwError(error.message);
}
},
I tried throwing the error directly with the same result.
Related
I have an app in Vue.js where user can post a review on a selected restaurant. To "POST" a review I have two functions linked to on #click event:
<button
class="btn btn-sm btn-primary mt-2 mr-2"
#click="addRestaurant(), addReview()"
>Add a Review</button>
Here is my two functions:
addRestaurant () {
let endpoint = `/api/restaurant/`;
let method = "POST";
apiService(endpoint, method, { maps: this.$route.params.maps, adress: this.$route.params.adress, name: this.$route.params.name })
},
addReview () {
let endpoint = `/api/restaurant_review/`;
let method = "POST";
apiService(endpoint, method, { maps: this.$route.params.maps, review_author: 1 })
},
It does work most of the time but I am facing two problems:
-If a restaurant instance allready exists, it throws a HTTP 400 error in my console. I've tried to catch it but with no success.
-It seems that sometimes addReview() is execued before addRestaurant() and I get an error because
this.$route.params.maps has a ForeignKey constraint.
So I tried to make only one function, with my two functions inside, trying to make addReview() executing as soon as addRestaurant() is done but I couldn't find a way. I also tried to make an if statement to check if my restaurant instance existed but I don't know if making so much API call in a row is good practice.
Any help is welcome I you think about the right solution to handle my problem
This my API service:
function handleResponse(response) {
if (response.status === 204) {
return '';
} else if (response.status === 404) {
return null;
} else if (response.status === 400) {
console.log("Restaurant allready added!")
return null;
} else {
return response.json();
}
}
function apiService(endpoint, method, data) {
const config = {
method: method || "GET",
body: data !== undefined ? JSON.stringify(data) : null,
headers: {
'content-type': 'application/json',
'X-CSRFTOKEN': CSRF_TOKEN
}
};
return fetch(endpoint, config)
.then(handleResponse)
.catch(error => console.log(error))
}
export { apiService };
First of all, in your handleResponse you can throw an error if something is wrong. That way, the error will end up in the catch method of the fetch call:
function handleResponse(response) {
if (response.status === 204) {
throw new Error("No data")
} else if (response.status === 404) {
throw new Error("Wrong URL")
} else if (response.status === 400) {
throw new Error("Restaurant already added!")
} else {
return response.json();
}
}
If handleresponse goes well, the fetch call will return a promise, but you're not handling that promise when you call apiService. I think it should look something like this:
addRestaurant () {
let endpoint = `/api/restaurant/`;
let method = "POST";
let config = { maps: this.$route.params.maps, adress: this.$route.params.adress, name: this.$route.params.name };
apiService(endpoint, method, config)
.then(data => {
console.log("restaurant added!" + data);
// now you can call the add review function
addReview();
})
.catch(error => console.log(error));
},
My problem is the next:
//express server
app.post('/register', (req, res) => {
const {
password,
passwordConfirm
} = req.body;
if (password === passwordConfirm) {
//...
} else {
res.status(400).json("Passwords aren't matching")
}
})
//react function
onSubmitSignIn = () => {
const { password, passwordConfirm } = this.state;
let data = new FormData();
data.append('password', password);
data.append('passwordConfirm', passwordConfirm);
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => response.json())
.then(user => {
//logs error message here
console.log(user)
})
//but I want to catch it here, and set the message to the state
.catch(alert => this.setState({alert}))
}
When I send the status code, and the message from express as the response, the front-end obviously recognize it as the response, that's why it logs the message to the console as "user". But how to send error which goes to the catch function?
fetch will really only error if it for some reason can't reason the API. In other words it will error on network errors. It will not explicitly error for non 2XX status codes.
You need to check the ok property as described here:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
--
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => {
if (!response.ok) {
throw new Error('my api returned an error')
}
return response.json()
})
.then(user => {
console.log(user)
})
.catch(alert => this.setState({alert}))
The problem is, that fetch is not recognizing the HTTP errors as Promise rejections.
The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally, and it will only reject on network failure or if anything prevented the request from completing.
(Source)
You can checkout the linked source of the fetch repo which also states a suggestion for handling HTTP error statuses.
What if you throw an error :
app.get("/", function (req, res) {
throw new Error("BROKEN"); // Express will catch this on its own.
});
And then catch this error in the front end ?
See here for reference
EDIT
Maybe should you return the error with return next() so that the rest of the code is not processed in the server method :
app.get("/", function (req, res) {
return next(new Error('BROKEN'));
});
//express server
app.post('/register', (req, res) => {
try {
const {
password,
passwordConfirm
} = req.body;
if (password === passwordConfirm) {
//...
} else {
res.status(400).json("Passwords aren't matching")
}
} catch (error) {
res.status(500).send(error);
}
})
//react function
onSubmitSignIn = () => {
const {
password,
passwordConfirm
} = this.state;
let data = new FormData();
data.append('password', password);
data.append('passwordConfirm', passwordConfirm);
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => response.json())
.then(user => {
//logs error message here
console.log(user)
})
//but I want to catch it here, and set the message to the state
.catch(alert => this.setState({
alert
}))
}
i'm working with an API that allows me to sync data to a local DB. There is a syncReady API that I'm calling recursively until the sync batch is ready to start sending data. The recursion is working correctly and the .then callback is called, but the resolve function never resolves the response.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
return new Promise((res, rej) => {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
res(response);
}, (error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
this.syncReady(token, batchID);
} else rej(error);
}
);
});
};
I get the 497 logged and the "The Response: {"pagesTotal";0}" response but the res(response) never sends the response down the chain. I've added a console.log message along the entire chain and none of the .then functions back down the chain are firing.
I hope I've explained this well enough :-). Any ideas why the promise isn't resolving?
Thanks!
First, you don't need to wrap something that returns a promise with a new Promise. Second, for your error case you don't resolve the promise if it is 497.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
return request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
return response;
})
.catch((error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
return this.syncReady(token, batchID);
} else {
throw error;
}
})
);
};
Maybe something like the above will work for you instead. Maybe try the above instead. As a general rule of thumb, it's you almost always want to return a Promise.
im using nodejs 8. I've replaced promise structure code to use async and await.
I have an issue when I need to return an object but await sentence resolve undefined.
This is my controller method:
request.create = async (id, params) => {
try {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
Property.findById(id).then( async (property) => {
property.requests.push(request._id)
await property.save()
let response = {
status: 200,
message: lang.__('general.success.created','Request')
}
return Promise.resolve(response)
})
}
}
catch (err) {
let response = {
status: 400,
message: lang.__('general.error.fatalError')
}
return Promise.reject(response)
}
}
In http request function:
exports.create = async (req, res) => {
try {
let response = await Request.create(req.params.id, req.body)
console.log(response)
res.send(response)
}
catch (err) {
res.status(err.status).send(err)
}
}
I tried returning Promise.resolve(response) and Promise.reject(response) with then and catch in the middleware function and is occurring the same.
What's wrong?
Thanks a lot, cheers
You don't necessarily need to interact with the promises at all inside an async function. Inside an async function, the regular throw syntax is the same as return Promise.reject() because an async function always returns a Promise. Another thing I noticed with your code is that you're rejecting promises inside a HTTP handler, which will definitely lead to unexpected behavior later on. You should instead handle all errors directly in the handler and act on them accordingly, instead of returning/throwing them.
Your code could be rewritten like so:
request.create = async (id, params) => {
let request = await new Request(Object.assign(params, { property : id })).save()
if ('_id' in request) {
let property = await Property.findById(id)
property.requests.push(request._id)
await property.save()
}
}
And your http handler:
exports.create = async (req, res) => {
try {
await Request.create(req.params.id, req.body)
res.send({
status: 200,
message: lang.__('general.success.created','Request')
})
} catch (err) {
switch (err.constructor) {
case DatabaseConnectionError: // Not connected to database
return res.sendStatus(500) // Internal server error
case UnauthorizedError:
return res.sendStatus(401) // Unauthorized
case default:
return res.status(400).send(err) // Generic error
}
}
}
Error classes:
class DatabaseConnectionError extends Error {}
class UnauthorizedError extends Error {}
Because you have that try/catch block inside your http handler method, anything that throws or rejects inside the Request.create method will be caught there. See https://repl.it/LtLo/3 for a more concise example of how errors thrown from async function or Promises doesn't need to be caught directly where they are first called from.
I have the following 2 methods.
But in the get method that I'm overriding from the Http module, the authentication success callback is called after it has already executed the request and returned a response. This way it's adding
the JWT token to the headers in the wrong order, and too late.
I'm not super knowledgeable with promises and observables.. But what can I do so that it actually waits for the callback to be done before executing the request and returning the response?
authenticate(authCompletedCallback, errorCallback) {
let authContext = new Microsoft.ADAL.AuthenticationContext(AUTHORITY_URL);
authContext.tokenCache.readItems().then((items) => {
if (items.length > 0) {
AUTHORITY_URL = items[0].authority;
authContext = new Microsoft.ADAL.AuthenticationContext(AUTHORITY_URL);
}
// Attempt to authorize user silently.
authContext
.acquireTokenSilentAsync(RESOURCE_URL, CLIENT_ID)
.then(authCompletedCallback, () => {
// We require user credentials so trigger authentication dialog.
authContext
.acquireTokenAsync(RESOURCE_URL, CLIENT_ID, REDIRECT_URL)
.then(authCompletedCallback, errorCallback);
});
});
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.authenticate((authResponse) => {
// This is executed second.
if (!options) {
options = { headers: new Headers() };
}
options.headers.set('Authorization', 'Bearer ' + authResponse.accessToken);
}, (err) => {
alert(JSON.stringify(err));
});
// This is executed first.
return super.get(url, options);
}
You might want to make the get function return a promise, since the actual Response object shouldn't exist until after you have authenticated. It looks like you're overriding a method, though, so maybe instead you'll want to make a different method.
getWithAuthentication(url: string, options?: RequestOptionsArgs): Promise<Observable<Response>> {
return new Promise((resolve, reject) => {
this.authenticate((authResponse) => {
if (!options) {
options = { headers: new Headers() };
}
options.headers.set('Authorization', 'Bearer ' + authResponse.accessToken);
let response = super.get(url, options);
resolve(response);
}, (err) => {
alert(JSON.stringify(err));
reject(err);
});
}
}