I apologize if this isn't a good question, but it's something that's confusing me a bit.
I'm attempting to return specific data from an $http.post() from within a factory, however it would appear that $http always return the original promise. I'm looking to avoid .success and .error given their possible depreciation in v1.5. Given that the factory might do other things such as set items in localStorage etc, I do not want to return $http.post() directly.
Anyways, is the following the best way to return specific data from an angular $http promise?
function login (email, password) {
var deferred = $q.defer();
$http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
return deferred.resolve('success');
})
.catch(function (data) {
return deferred.reject('fail');
});
return deferred.promise;
}
You don't need to create a deferred object. Instead, you can just return the result from $http.post. $http.post returns a promise that happens to have an extra two methods (success and failure).
function login(email, password) {
return $http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
var newData = translateData(data);
//now the data will be passed to the next promise
return newData;
})
.catch(function (reason) {
/*do stuff with failure*/
//Now the rejection reason will be propagated to the next promise
return $q.reject(reason);
});
}
login()
//You should get your data here.
.then(function (data) { console.log(data); })
.catch(function (reason) { console.log(reason); });
You may be interested to read this blog post which explains how to propagate data and rejection reasons through a promise chain.
I would have written it with the error response as 2nd callback of the 'then' method (my example below). This way the error callback will only get called if there is an error with that $http request.
function login (email, password) {
var deferred = $q.defer();
$http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
return deferred.resolve(data);
}, function (message) {
return deferred.reject(message);
});
return deferred.promise;
}
The way you have done it - using catch() - means it will get called if anything goes wrong in a promise chain. Hence, catch() would most likely be used at the end of several promises. For example, Something like this
CustomerService.login(email, password)
.then(getUserData)
.then(setUpAccount)
.catch($log.error);
See this great post, which explains it far better than I did
Also, check out the docs on promises, the section on 'The Promise API'
Related
In my Controller:
function login(credentials) {
AuthService
.login(credentials)
.then(successCallback, errorCallback);
//same issue with .then(successCallback).catch(errorCallback);
}
function successCallback() {
// do something after success
}
function errorCallback(data) {
// do something after error
}
and in my AuthService:
authService.login = function (credentials) {
return $http
.post(ENV.apiEndpoint + 'api/v1/login_check', credentials)
.then(
function (result) {
Session.create(result.data.token, result.data.data);
},
function (data) {
Messages.create('Login failed: ' + data.statusText);
}
);
}
When my POST delivers a 200 response code, everything works as expected do something after success is executed.
But when my POST results e.g. in a 401 I can see that Messages.create is called (so in this case it enters the error path), but unfortunately my Controller calls the successCallback and not the errorCallback.
I had to migrate this because I was using the deprecated and since Angular 1.6 removed .success and .error promise attributes. It was working back then, but after migration this doesn't work anymore.
What am I doing wrong here?
You may reject the promise in your error callback.
authService.login = function (credentials) {
return $http
.post(ENV.apiEndpoint + 'api/v1/login_check', credentials)
.then(
function (result) {
Session.create(result.data.token, result.data.data);
},
function (data) {
Messages.create('Login failed: ' + data.statusText);
return $q.reject(data);
}
);
}
From Angular $q doc:
reject(reason);
Creates a promise that is resolved as rejected with the specified
reason. This api should be used to forward rejection in a chain of
promises. If you are dealing with the last promise in a promise chain,
you don't need to worry about it.
When comparing deferreds/promises to the familiar behavior of
try/catch/throw, think of reject as the throw keyword in JavaScript.
This also means that if you "catch" an error via a promise error
callback and you want to forward the error to the promise derived from
the current promise, you have to "rethrow" the error by returning a
rejection constructed via reject.
I don't understand below part
var q = require("q"),
BlogPost = require("../models/blogPost");
module.exports = {
getAllPosts: getAllPosts
};
function getAllPosts() {
var deferred = q.defer();
BlogPost
.find({})
.sort("-date")
.exec(function(error, posts) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(posts);
}
});
return deferred.promise;
}
I found above code in controller, but can't understand it. Why at the end we use return deferred.promise? How would I use the getAllPosts later? Can't we just return the posts objects?
You would consume a function that returns a promise like so:
var getAllPosts = require('./your-module-name.js').getAllPosts;
getAllPosts()
.then(function(posts) {
//successfully process the posts
})
.catch(function(err) {
//handle the error
})
.finally(function(){
//clean up anything you need to...
})
Promise is the just representation of the asynchronous result.
It has three states:
1-> success
-> Error
-> Pending
deferred is a just object of promise and it returns after processing one of above state.
We can use javascript code without promise but sometimes we have to give it to make our code execute as asynchronous way.
That's why we use promise
I am trying to work through JS Promises in node.js and don't get the solution for passing promises between different function.
The task
For a main logic, I need to get a json object of items from a REST API. The API handling itself is located in a api.js file.
The request to the API inthere is made through the request-promise module. I have a private makeRequest function and public helper functions, like API.getItems().
The main logic in index.js needs to wait for the API function until it can be executed.
Questions
The promise passing kind of works, but I am not sure if this is more than a coincidence. Is it correct to return a Promise which returns the responses in makeRequest?
Do I really need all the promises to make the main logic work only after waiting for the items to be setup? Is there a simpler way?
I still need to figure out, how to best handle errors from a) the makeRequest and b) the getItems functions. What's the best practice with Promises therefor? Passing Error objects?
Here is the Code that I came up with right now:
// index.js
var API = require('./lib/api');
var items;
function mainLogic() {
if (items instanceof Error) {
console.log("No items present. Stopping main logic.");
return;
}
// ... do something with items
}
API.getItems().then(function (response) {
if (response) {
console.log(response);
items = response;
mainLogic();
}
}, function (err) {
console.log(err);
});
api.js
// ./lib/api.js
var request = require('request-promise');
// constructor
var API = function () {
var api = this;
api.endpoint = "https://api.example.com/v1";
//...
};
API.prototype.getItems = function () {
var api = this;
var endpoint = '/items';
return new Promise(function (resolve, reject) {
var request = makeRequest(api, endpoint).then(function (response) {
if (200 === response.statusCode) {
resolve(response.body.items);
}
}, function (err) {
reject(false);
});
});
};
function makeRequest(api, endpoint) {
var url = api.endpoint + endpoint;
var options = {
method: 'GET',
uri: url,
body: {},
headers: {},
simple: false,
resolveWithFullResponse: true,
json: true
};
return request(options)
.then(function (response) {
console.log(response.body);
return response;
})
.catch(function (err) {
return Error(err);
});
}
module.exports = new API();
Some more background:
At first I started to make API request with the request module, that works with callbacks. Since these were called async, the items never made it to the main logic and I used to handle it with Promises.
You are missing two things here:
That you can chain promises directly and
the way promise error handling works.
You can change the return statement in makeRequest() to:
return request(options);
Since makeRequest() returns a promise, you can reuse it in getItems() and you don't have to create a new promise explicitly. The .then() function already does this for you:
return makeRequest(api, endpoint)
.then(function (response) {
if (200 === response.statusCode) {
return response.body.items;
}
else {
// throw an exception or call Promise.reject() with a proper error
}
});
If the promise returned by makeRequest() was rejected and you don't handle rejection -- like in the above code --, the promise returned by .then() will also be rejected. You can compare the behaviour to exceptions. If you don't catch one, it bubbles up the callstack.
Finally, in index.js you should use getItems() like this:
API.getItems().then(function (response) {
// Here you are sure that everything worked. No additional checks required.
// Whatever you want to do with the response, do it here.
// Don't assign response to another variable outside of this scope.
// If processing the response is complex, rather pass it to another
// function directly.
}, function (err) {
// handle the error
});
I recommend this blog post to better understand the concept of promises:
https://blog.domenic.me/youre-missing-the-point-of-promises/
as I understand it Angular http has 2 checks 'success and 'error'. Thats in terms of connecting to the service or not - so I have that in hand and thats my first check.
The issue I have is that the data in my JSON has a success state which informs me if the data it contains or has received from my form had any problems with it, in which case there will be an error object that I act on and display to the user.
I need to check for that value of success, but where is the best place to check for that?
Should I be doing it in the controller?
Without that data being correct theres nothing else for the page to do so it is effectively the first thing that needs to be done after the data is retrieved.
heres the basic controller layout
app.controller("dataCtrl", function ($scope, $http) {
$http.post('/getdata').success(function (data) {
$scope.businessData = data;
// Should I then be checking businessData.success at this level?
}).error(function () {
alert("Problem");
});
});
You can write something like this:
$http.post('/getdata').success(function (data) {
if (validate(data)) {
$scope.businessData = data;
} else {
$scop.buisnessDataError = {msg: 'smth bad happend'};
}
}).error(function () {..})
Otherwise, you can write your validator in Promise-like style and then just chain promises in such manner:
$http.post('/getdata').then(function (res) {
return validator(null, res.data);
}, function (err) {
return validator({msg: 'error'})
}).then(function (data) {
//proceed your data
}, function (err) {
alert(err.msg);
});
Where validator is:
var varlidator = function (err, data) {
return $q(function (resolve, reject) {
if (/*data is not valid*/ || err) {
reject(err);
} else {
resolve(data);
}
});
}
$q is a standard angulars implementation of Promises
I am doing a http.post request and I am trying to display error message in case anything goes wrong. I am handling the error message in the service, then passing it to the controller and setting it in the $scope. I do not get any JS errors.... any ideas why that would be?
services.js
angular.module('app.services', [])
.factory('Model', function($http) {
Model.save = function(data) {
return $http.post(url, data)
.success(function(data, status, headers) {
console.log(data);
console.log(status);
console.log(headers);
})
.error(function(data, status, headers) {
var requestError = 'Something went wrong! :(';
return requestError; //pass error message back to $scope
});
}
return Model;
});
controllers.js
.controller('Ctrl', function($scope, Model) {
//form object data
$scope.formInfo = {};
//form save
$scope.saveData = function() {
//console.log($scope.formInfo);
$scope.requestError = '';
//form data
var data = {name: $scope.formInfo.name, description: $scope.formInfo.description, token: "/api/v1/tokens/1/"};
Model.save(data).then(function(requestError) {
alert(requestError);
if (requestError === '') {
//do nothing for now
}
else {
$scope.requestError = requestError;
}
});
};
})
A promise has two end states: resolved (success) or rejected (error).
In the controller, the then() function needs two different handlers if you want to access both states. The first handler only receives resolved (success) promises from the service. To also handle rejected (error) promises from the service, you'll need something like this:
Model.save(data).then(function(success) { ... }, function(error) { ... });
If you only care about the errors for some reason, use either of these (which are equivalent):
Model.save(data).then(null, function(error) { ... });
Model.save(data).catch(function(error) { ... });
In your service, make sure you're actually returned a rejected (error) promise back to the controller in the event of an error. You do this by using return $q.reject(requestError) instead of just return requestError.
Important note: Since you're using $http, this will already reject promises in the case of an HTTP error. If you don't handle rejected (error) promises in the service, they'll automatically be passed through to the controller's error handler. You may want to simply let $http's rejection pass through, and not have an error handler in the service. You can do this by simply removing .error() in the service. This allows the HTTP error to be handled by the controller directly. (This may or may not be desirable, depending on what kind of error handling you want to do).
The basic pattern I tend to follow with my HTTP services looks like this, which returns either a resolved promise with just the downloaded data, or returns the rejected promise (error) that comes out of $http directly to the controller:
return $http.get(config).then(function(success) { return success.data });