I have following function, which returns promise for authentication
authenticate: function(options) {
return new Promise((resolve, reject) => {
$.ajax({
url: this.tokenEndpoint,
type: 'POST',
headers: {'Content-Type': 'application/json', 'Accept-Encoding': 'gzip, deflate'},
data: JSON.stringify({
username: options.identification,
password: options.password
})
}).then(function(response) {
console.log(response) // I get here object with good properties
resolve({
lastLoginDate: response.lastLoginDate,
login: response.login,
name: response.name,
role: response.role,
token: response.id_token
});
}, function(xhr, status, error) {
if(error !== undefined) {
console.log(error);
}
var response = xhr.responseText;
reject(response);
});
});
When i call it and pass good username and password it returns "undefined", but when i pass bad properties, my "reject" works perfectly . Does someone know why my "then" doesn't return expected output?
this.get('session').authenticate('authenticator:custom', {'identification': identification, 'password': password})
.then((data)=>{
console.log(data); //Undefined <- here is my problem.
})
.catch((reason) => {
console.log(reason); // It works, when pass bad password!
this.set('errorMessage', reason.error);
});
Firstly, $.ajax returns a jQuery Promise which these days is 99.999% as good as a native Promise - so no need to wrap $.ajax in a Promise
Secondly, .then takes two callback arguments, onfullfilled and onrejected, both of these callbacks only receive a single argument - your onrejected callback will never have status and error arguments - so that code is flawed
In the onfullfilled callback, the argument is an array, being the [result, status, jqXHR] that you would get in the $.ajax success call
Similarly, in the onrejected callback, the argument is an array, being [jqXHR, status, errorThrown]
Given all that, the authenticate function can be written
authenticate: function(options) {
return $.ajax({
url: this.tokenEndpoint,
type: 'POST',
headers: {'Content-Type': 'application/json', 'Accept-Encoding': 'gzip, deflate'},
data: JSON.stringify({
username: options.identification,
password: options.password
})
})
.then(function(arr) {
var response = arr[0];
console.log(response);
// return instead of resolve
return({
lastLoginDate: response.lastLoginDate,
login: response.login,
name: response.name,
role: response.role,
token: response.id_token
});
}, function(arr) {
var xhr = arr[0], status = arr[1], error = arr[2];
if(error !== undefined) {
console.log(error);
}
var response = xhr.responseText;
// return a rejection - could "throw response" instead
return Promise.reject(response);
}
);
}
and FYI - ES2015+ you could write
.then(function([response, status, xhr]) {
.....
}, function([xhr, status, error]) {
....
Related
Background:
I'm trying the simple web push notifications example given in Google's documentation of the same (link: https://developers.google.com/web/fundamentals/push-notifications/subscribing-a-user)
I keep running into a SyntaxError: JSON.parse: unexpected character at line 2 column 1 of the JSON data error, which means I'm doing something fundamentally wrong. I'm a JS neophyte, can you help?
My simple function to subscribe user to push is simply:
function subscribeUserToPush() {
const pub_key = document.getElementById("notif_pub_key");
const service_worker_location = document.getElementById("sw_loc");
return navigator.serviceWorker.register(service_worker_location.value)
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(pub_key.value)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
sendSubscriptionToBackEnd(pushSubscription);
return pushSubscription;
});
}
And sendSubscriptionToBackEnd() essentially uses fetch like so:
function sendSubscriptionToBackEnd(subscription) {
const sub_obj = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CSRFToken':get_cookie('csrftoken')
},
body: JSON.stringify(subscription)
}
return fetch('/subscription/save/', sub_obj)
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
This fails with the error SyntaxError: JSON.parse: unexpected character at line 2 column 1 of the JSON data.
Doing console.log(sub_obj) shows this object:
Object { method: "POST", headers: {…}, body: "{\"endpoint\":\"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABcaErG3Zn6Urzn5Hfhpyjl0eJg_IVtcgZI-sQr5KGE0WEWt9mKjYb7YXU60wgJtj9gYusApIJnObN0Vvm7oJFRXhbehxtSFxqHLOhSt9MvbIg0tQancpNAcSZ3fWA89E-W6hu0x4dqzqnxqP9KeQ42MYZnelO_IK7Ao1cWlJ41w8wZSlc\",\"keys\":{\"auth\":\"AJfXcUMO3ciEZL1DdD2AbA\",\"p256dh\":\"BN84oKD3-vFqlJnLU4IY7qgmPeSG96un-DttKZnSJhrFMWwLrH2j1a0tTB_QLoq5oLCAQql6hLDJ1W4hgnFQQUs\"}}" }
Also doing console.log(response); right before return response.json(); displays:
Response { type: "basic", url: "http://127.0.0.1:8001/subscription/save/", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: false }
What's the problem and how do I fix it?
Changing return response.json() to return response.text() and then doing a console log on responseData gives the entire HTML of the page. I end up with the error Error: Bad response from server.
The main issue was that CSRFToken was mislabeled when being set in sendSubscriptionToBackEnd. It should have been X-CSRFToken. I.e.
function sendSubscriptionToBackEnd(subscription) {
const sub_obj = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken':get_cookie('csrftoken'),
},
body: JSON.stringify(subscription)
}
return fetch('/subscription/save/', sub_obj)
.then(function(response) {
console.log(response);
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
// response from the server
console.log(responseData)
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
So why was this leading to return response.json(); failing?
Because the project in question routes requests that fail csrf checks to a default view - one which doesn't return a json response at all. Mystery solved!
I guess that fetch returns a promise. But how do I handle it nicely? The code below does not quite work. I get {message: "Internal server error custom: TypeError: Cannot read property 'then' of undefined"}.
exports.handler = (event, context, callback) => {
try {
getDiscourseId(username, callback).then((userId) => {
callback(null, {
statusCode: 200,
headers: {},
body: JSON.stringify({
userId: userId
})
});
});
} catch (error) {
callback(null, {
statusCode: 500,
headers: {},
body: JSON.stringify({
message: "Internal server error custom: " + error
})
});
}
};
function getDiscourseId(username) {
console.log({username: username, discourseApiKey: discourseApiKey, discourseApiUser: discourseApiUser})
fetch(`https://${domain}/users/${username}.json?api_key=${discourseApiKey}&api_username=${discourseApiUser}`, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
method: 'GET',
})
.then(response => {
return response.json();
})
.then(data => {
if (data) {
return data.user.id;
}
})
.catch(err => {
return {err: err};
});
}
You're getting that error because your getDiscourseId function does not return a value.
If you add the keyword return in front of your fetch(...) call, you should start making some headway.
You'll probably also want to remove the .catch from inside getDiscourseId and instead add it to the end of the call to getDiscourseId inside your handler:
exports.handler = (event, context, callback) => {
getDiscourseId(username)
.then((userId) => {
callback(null, {
statusCode: 200,
headers: {},
body: JSON.stringify({
userId: userId
})
});
})
.catch(error => {
callback(null, {
statusCode: 500,
headers: {},
body: JSON.stringify({
message: "Internal server error custom: " + error
})
});
});
};
function getDiscourseId(username) {
console.log({username: username, discourseApiKey: discourseApiKey, discourseApiUser: discourseApiUser})
return fetch(`https://${domain}/users/${username}.json?api_key=${discourseApiKey}&api_username=${discourseApiUser}`, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
method: 'GET',
})
.then(response => {
if (!response.ok) { // h/t TJ Crowder
throw new Error("Failed with HTTP code " + response.status);
}
return response.json();
})
.then(data => {
if (data) {
return data.user.id;
}
});
}
EDIT: TJ Crowder is correct that you probably want to treat 4xx and 5xx responses as full-fledged errors. I shamelessly stole his example code from his blog and added it to the above.
Stop when you return the response.json(). This returns a promise, for which .then can be used.
You are returning the userId which .then cannot be used for.
If you stop at the return response.json(), you can use the '.then' statement that you already have (data.user.id => ...).
I have a function which uses jquery to call API and get a result. My API end is programmed to return the number "19" just for testing.
export function clientAdd(data) {
return (dispatch) => {
return $.ajax({
url: "http://api.example.com/client/add/",
headers: {'AUTHORIZATION': `${sessionStorage.jwt}`},
type: 'POST',
cache: false,
data: data,
dataType: 'json',
success: function (data) {
let redirectUrl = '/client/' + data
return redirectUrl';
},
error: function(xhr, status, err) {
if (xhr.status === 401) {
sessionStorage.removeItem('jwt');
return '/signin';
}
console.log('xhr',xhr.responseText);
console.log('status',status);
console.log('err',err);
return dispatch({type: GET_CLIENT_FAIL, err});
}
})
}
}
Then in my component, upon clicking on the submit button, it will call the onSave function as follows
onSave(event) {
//event.preventDefault();
this.props.actions.clientAdd(this.state.credentials).then((result) => {
return this.setState({redirect: true, newCustomerId: result})
}).catch((result) => {
return this.setState({redirect: false, errorMessage: result})
});
}
Where the result is supposed to be the redirectUrl or ErrorMessage.
However, I'm keep getting the number 19 which is returned by my API.
I read online if I want to use promise in my component, i have to add return infront of $.ajax, if not "then" will be undefined.
What you can do is, create your own promise and put the ajax call inside it
Then call resolve and pass data that you want when then is called
resolve(data_passed_to_then)
Like this :
return new Promise((resolve,reject) => {
$.ajax({
...
success: function (data) {
let redirectUrl = '/client/' + data
resolve(redirectUrl);
},
error: function(xhr, status, err) {
...
// return dispatch({type: GET_CLIENT_FAIL, err});
reject(err);
}
})
})
I have the following function which came from the mern.io stack.
export default function callApi(endpoint, method = 'get', body) {
return fetch(`${API_URL}/${endpoint}`, {
headers: { 'content-type': 'application/json' },
method,
body: JSON.stringify(body),
})
.then(response => response.json().then(json => ({ json, response })))
.then(({ json, response }) => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
.then(
response => response,
error => error
);
}
I call the function the following way
callApi('auth/register', 'post', {
username,
password,
}).then(function(res) {
// do stuff
});
The response will either be a 201 or a 422. How can handle the 422 the proper way? A 422 would occur if a username already exists.
Do I put all the logic in the first then such as the following:
callApi('auth/register', 'post', {
username,
password,
}).then(function(res) {
if (res.error) {
} else {
}
});
Assuming callApi returns a Promise object:
callApi('auth/register', 'post', {
username,
password,
}).then(function(res) {
// res
}).catch(function(err) {
// handle err
});
For more information: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
Service.js
this.userLogin = function (username, password) {
var dataBody = $.param({'username': username,'password': password});
return $http({
method: 'POST',
url: servicePathURL,
data: dataBody,
headers: {
"Authorization": "Basic",
"Content-Type": "application/x-www-form-urlencoded"
}
})
.then(function (response) {
$rootScope.globals = {
currentUser: {
username: username,
}
};
return response;
}).catch(function (error) {
throw error;
});
};
Controller.js
AuthenticationServiceLogin.userLogin($scope.username, $scope.password)
.then(function (response) {
if (response.status ==200) {
toaster.pop('success', "", "Login Successful");
$location.path('/home');
}
}).catch(function (error) {
toaster.pop('error', "", error.statusText);
});
In Controller.js, toaster.pop('error', "", error.statusText); is not being called when there is an exception while user logs in.
Also I have used $http method, is there any advantage to returning
a $q.defer() promise rather than an $http promise or considered as best practice ? If yes, how can I modify above $http code into promise ?
Your code appears to be fine. So long as you re-throw any errors encountered by your $http call, they should propagate all the way up to the controller. I'm inclined to say that any problems with your error handling are not in the code that you've posted.
There's no advantage to having a catch handler in service.js if you're not going to do any work with the error thrown. Service.js will want to look like this:
Service.js
this.userLogin = function (username, password) {
return $http({
method: 'POST',
url: servicePathURL,
data: $.param({'username': username,'password': password}),
headers: {
"Authorization": "Basic",
"Content-Type": "application/x-www-form-urlencoded"
}
})
.then(function (response) {
$rootScope.globals = {
currentUser: {
username: username,
}
};
return response;
// The catch block here is useless. The promise returned by $http will transmit any
// errors on its own.
//}).catch(function (error) {
// throw error;
});
};
In response to your second question: there is no advantage to using $q.defer() instead of returning the promise returned by $http itself, and a number of major disadvantages - namely that any exceptions thrown by your login method will disappear. See The Deferred Anti-pattern here for details: https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
$http is definitely the preferred option.