Forgive my ignorance, I'm new to Angular and JavaScript.
I've read that promises make it more elegant to call asynchronous services, but I've not been able to find any documentation on how to make my service asynchronous.
If you are simply wrapping a call to $http then there is no additional work needed on your part. All the $http calls return a promise, so you can just pass that along and your API will be async, and promise based.
function MyService($http){
this.$http = $http;
}
MyService.prototype = {
getContacts: function(){
//$http returns a promise already,
// so just pass it along
return $http.get('/api/contacts');
}
};
angular.service('myService', MyService);
//Later on in some controller
function MyController(myService){
var _this = this;
//getContacts() returns the promise
// from $http
myService.getContacts()
.success(function(contacts){
_this.contacts = contacts;
});
}
However...
If you want to create a normal API that executes code asynchronously, then you can wrap that up in a promise just like the example in the docs.
JavaScript is basically* single threaded, so that means that it uses an execution queue for asynchronous code. Execution will go from top to bottom, and whenever it stops, the queue will be emptied out in order.
When you call something like setTimeout or setInterval it is simply placing that code on the queue to be executed whenever the current thread gets around to it, but it isn't happening in the background.
You can test this for yourself, by opening up a browser console and typing this code into it:
setTimeout(function(){ console.log("I'm first!"); }, 0);
console.log("Nope, I am!");
Even though I have set the timeout to be 0 milliseconds, that doesn't mean it will execute immediately. It means it will be placed on the queue to be run immediately once all the other code finishes.
Finally
Try not to think of promises as strictly for managing asynchronous calls. They are a pattern for executing code once some precondition has been met. It just so happens that the most popular precondition is some asynchronous I/O via AJAX.
But the pattern is a great way of managing any operation that must wait for some number of preconditions.
Just to really drive this point home, check out this little snippet that uses a promise to determine when the user has clicked the button more than 5 times in a row.
(function() {
var app = angular.module('promise-example', []);
function PromiseBasedCounter($q, $timeout) {
this.$q = $q;
this.$timeout = $timeout;
this.counter = 0;
this.counterDef = $q.defer();
}
PromiseBasedCounter.$inject = ['$q', '$timeout'];
PromiseBasedCounter.prototype = {
increment: function() {
var _this = this;
//$timeout returns a promise, so we can
// just pass that along. Whatever is returned
// from the inner function will be the value of the
// resolved promise
return _this.$timeout(function() {
_this.counter += 1;
//Here we resolve the promise we created in the
// constructor only if the count is above 5
if (_this.counter > 5) {
_this.counterDef.resolve("Counter is above 5!");
}
return _this.counter;
});
},
countReached: function() {
//All defered objects have a 'promise' property
// that is an immutable version of the defered
// returning this means we can attach callbacks
// using the promise syntax to be executed (or not)
// at some point in the future.
return this.counterDef.promise;
}
};
app.service('promiseBasedCounter', PromiseBasedCounter);
function ClickCtrl(promiseBasedCounter) {
var _this = this;
_this.promiseBasedCounter = promiseBasedCounter;
_this.count = 0;
//Here we set up our callback. Notice this
// really has nothing to do with asyncronous I/O
// but we don't know when, or if, this code will be
// run in the future. However, it does provide an elegant
// way to handle some precondition without having to manually
// litter your code with the checks
_this.promiseBasedCounter.countReached()
.then(function(msg) {
_this.msg = msg;
});
}
ClickCtrl.$inject = ['promiseBasedCounter'];
ClickCtrl.prototype = {
incrementCounter: function() {
var _this = this;
//Whenever we increment, our service executes
// that code using $timeout and returns the promise.
// The promise is resolved with the current value of the counter.
_this.promiseBasedCounter.increment()
.then(function(count) {
_this.count = count;
});
}
};
app.controller('clickCtrl', ClickCtrl);
}());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="promise-example" ng-controller="clickCtrl as ctrl">
<div class="container">
<h1>The current count is: <small>{{ctrl.count}}</small></h1>
<button type="button" class="btn btn-lg btn-primary" ng-click="ctrl.incrementCounter()">Click Me!</button>
</div>
<div class="container">
<h1>{{ctrl.msg}}</h1>
</div>
</div>
Related
I am really new to angularJS. I need to develop a page where angular JS wait for a event to happen at server side so angular JS should keep checking server using $http call in every 2 seconds. Once that event completes Angular should not invoke any $http call to server again.
I tried different method but it gives me error like "Watchers fired in the last 5 iterations: []"
Please let me know how to do it.
Following is my code
HTML
<div ng-controller="myController">
<div id="divOnTop" ng-show="!isEventDone()">
<div class="render"></div>
</div>
</div>
Angular JS
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http) {
$scope.ready = false;
$scope.isEventDone = function () {
$scope.ready = $scope.getData();
return $scope.ready;
};
$scope.getData = function () {
if (! $scope.ready) {
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
});
}
};
setInterval($scope.isPageReady, 5000);
});
A few things here.
I'm not convinced the accepted answer actually works nor solves the initial problem. So, I'll share my 2 cents here.
$scope.ready = $scope.getData(); will set $scope.ready to undefined each time since this method doesn't return anything. Thus, ng-show="!isEventDone()" will always show the DOM.
You should use angular's $interval instead of setInterval for short-polling in angular.
Also, I've refactored some redundancy.
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $interval) {
var intervalPromise = $interval($scope.getData, 5000);
$scope.getData = function () {
if (! $scope.isEventDone) {
$http
.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
if($scope.isEventDone) {
$interval.cancel(intervalPromise);
}
});
}
else {
$interval.cancel(intervalPromise);
}
};
});
This should work and solve your initial problem. However, there's a scenario where your server may be on a high load and takes 3 seconds to respond. In this case, you're calling the server every 2 seconds because you're waiting for 5 seconds after the previous request has started and not waiting for after the previous request has ended.
A better solution than this is to use a module like async which easily handles asynchronous methods. Combining with $timeout:
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $timeout) {
var getData = function(cb){
if(!$scope.isEventDone) return cb();
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
cb();
});
};
// do during will run getData at least once
async.doDuring(getData, function test(err, cb) {
// asynchronous test method to see if loop should still occur
// call callback 5 seconds after getData has responded
// instead of counting 5 seconds after getData initiated the request
$timeout(function(){
cb(null, !$scope.isEventDone);
// if second param is true, call `getData()` again otherwise, end the loop
}, 5000);
}, function(err) {
console.log(err);
// if you're here, either error has occurred or
// the loop has ended with `$scope.isEventDone = true`
});
});
This will call the timeout after the request has ended.
A better alternative, if you have control of the server, is to use a websocket which will enable long-polling (server notifies the client instead of client making frequent requests) and this will not increase significant load on the server as clients grow.
I hope this helps
In your example $scope.pageIsReady does not exist. What you could do is inject the $timeout service into your controller and wrap your http call inside of it:
var timeoutInstance = $timeout(function(){
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
if($scope.ready){
$timeout.cancel(timeoutInstance);
else
$scope.getData();
}
});
},5000);
cancel will stop the timeout from being called. I have not tested this but it should be along those lines.
Also not sure what type of backend you are using but if it is .net you could look into SignalR which uses sockets so the server side tells the front end when it is ready and therefore you no longer need to use polling.
I've a service like this:
angular.module('module')
.factory('UserList', function ($http) {
return {
getUserList: $http.get('/portal-services/NemesysPortalBackend/rest/user/all')
};
});
this constraint me to do
UserList.getUserList.then(function(res){$scope.data = res.data;})
in every controller where I need it.
Is there any way to "facade" it to simply have
$scope.data = UserList.getUserList();
Thanks
I'm making an assumption here that the user list doesn't change often, so this way you can cache it to a variable... otherwise you will need to make a call each time you expect the list changes (or reload the list using an interval?)
Fetching data before it's really needed is called "Eager loading"
angular.module('module').factory('UserList', function ($http, $q, $interval) { // import $q as well
var userList = []; // initialized as blank array
var refreshList = function(){
var deferred = $q.defer();
$http.get('/portal-services/NemesysPortalBackend/rest/user/all').then(
function(successResponse){
userList = successResponse.data;
deferred.resolve(successResponse);
},function(failureResponse){
// do something on error?
deferred.reject(failureResponse);
});
return deferred.promise;
}
refreshList(); // eager load, run right away
// i don't recommend this next line, there are better ways of doing this
$interval(refreshList(), 1000*60); // get new list every 60 seconds
return {
getUserList: function(){ return userList; }, // return user list only
refreshList: function(){ refreshList(); } // return promise which getting new list
};
});
Again, I don't recommend using $interval to reload the list, but instead call refreshList any time updates are made to the user list
Ex:
angular.module('module').controller('userCtrl', function(UserList) {
$scope.data = UserList.getUserList();
// once you change the user list, call a refresh
UserList.addUser().then(UserList.refreshList()).then(function(){
$scope.data = UserList.getUserList();
);
});
You cannot do this because JavaScript is single-threaded. This means, that when you use some call that is asynchronous, you cannot ever make it synchronous. There is no waiting(*) in Javascript. You cannot block your function call to wait for the results from the server.
Even if you tried as hard as this:
function getResult() {
var result;
UserList.getUserList.then(function(res) {
result = res.data; // this should break the loop below
});
while (!result) {}; // active waiting, wasting CPU cycles
return result;
}
...it wouldn't work because the callback function will not be executed until the currently running code (i.e., the infinite loop) finishes. An infinite loop like this would freeze the whole application forever.
(*) this doesn't mean that you cannot schedule functions to be called at some point later. Promises and closures help a lot with it.
I have the following controller code:
.controller('Controller1', function ($scope, MyService) {
var promise = MyService.getData();
promise.then(function(success) {
console.log("success");
}, function(error) {
console.log("error");
}, function(update) {
console.log("got an update!");
}) ;
}
And in my services.js:
.factory('MyService', function ($resource, API_END_POINT, localStorageService, $q) {
return {
getData: function() {
var resource = $resource(API_END_POINT + '/data', {
query: { method: 'GET', isArray: true }
});
var deferred = $q.defer();
var response = localStorageService.get("data");
console.log("from local storage: "+JSON.stringify(response));
deferred.notify(response);
resource.query(function (success) {
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
console.log("error occurred");
deferred.reject(response);
});
return deferred.promise;
}
}
})
But for some reason the deferred.notify call never seems to execute and be received within the controller. Have I don't something wrong here? I'm not sure how to get the notify to execute.
I managed to get it working by wrapping notify in $timeout function:
$timeout(function() {
deferred.notify('In progress')
}, 0)
Looks like you cant call notify before you return promise object, that kinda makes sense.
source: http://www.bennadel.com/blog/2800-forcing-q-notify-to-execute-with-a-no-op-in-angularjs.htm
Forcing $q .notify() To Execute
The beauty of the .notify() event is that our data-access layer can use it serve up the "immediately available, yet stale" data while still using the .resolve() event for nothing but live data. This gives the calling context - your controller - great insight and control over which dataset is cached and whether or not it [the controller] even wants to incorporate cached data.
But, we run into a little bit of a race condition. The data-access service, that owns the cached data, needs to call .notify() before it returns the promise to the calling context. This means that your controller binds to the notify event after .notify() has been called. From a philosophical standpoint, this should be fine - Promises (and just about everything that is event-driven) are intended to invoke bindings asynchronously in order to create uniformity of access.
From a practical standpoint, however, it's not quite that simple. While AngularJS follows this philosophy, it also adds a few optimizations to cut down on processing. In our case specifically, AngularJS won't schedule the callback-processing in a deferred object unless it sees that at least one callback is bound (otherwise it thinks the world isn't listening). As such, our controller will never be notified about the cached data.
To get around this, we can have our service layer bind a no-op (no operation) function to the notify event before it calls .notify(). This way, when it does call .notify(), AngularJS will see that at least one callback is registered and it will scheduled a flushing of the pending queue in the next tick (which is implemented via $rootScope.$evalAsync()). This allows our controller to get notified of cached data even if it binds to the notify event after .notify() has been invoked.
To see this in action, I've created a friendService that returns data through two different methods. Both of the methods attempt to return cached data via .notify() and then "live" data via .resolve(). The only difference between the two methods is that one binds a no-op to the notify event before calling .notify()
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</h1>
<h2>
Friends
</h2>
<div ng-switch="isLoading">
<!-- Show while friends are being loaded. -->
<p ng-switch-when="true">
<em>Loading...</em>
</p>
<!-- Show once the friends have loaded and are available in the view-model. -->
<ul ng-switch-when="false">
<li ng-repeat="friend in friends track by friend.id">
{{ friend.name }}
</li>
</ul>
</div>
<p>
<a ng-click="load()">Load</a>
|
<a ng-click="loadWithNoop()">Load With No-Op</a>
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, friendService ) {
$scope.isLoading = false;
$scope.friends = [];
// Load the friend data (defaults to "get" method vs. "getWithNoop").
loadRemoteData();
// ---
// PUBLIC METHODS.
// ---
// I reload the list of friends using friendService.get().
$scope.load = function() {
loadRemoteData( "get" );
};
// I reload the list of friends using friendService.getWithNoop().
$scope.loadWithNoop = function() {
loadRemoteData( "getWithNoop" );
};
// ---
// PRIVATE METHODS.
// ---
// I load the friends from the friend repository. I am passing-in the
// method name to demonstrate that, from the Controller's point-of-view,
// nothing here is different other than the name of the method. The real
// substantive difference exists in the implementation of the friend-
// Service method and how it interacts with $q / Deferred.
function loadRemoteData( loadingMethod ) {
console.info( "Loading friends with [", loadingMethod, "]" );
// Indicate that we are in the loading phase.
$scope.isLoading = true;
// When we make the request, we expect the service to try to use
// cached-data, which it will make available via the "notify" event
// handler on the promise. As such, we're going to wire up the same
// event handler to both the "resolve" and the "notify" callbacks.
friendService[ loadingMethod || "get" ]
.call( friendService )
.then(
handleResolve, // Resolve.
null,
handleResolve // Notify.
)
;
function handleResolve( friends ) {
// Indicate that the data is no longer being loaded.
$scope.isLoading = false;
$scope.friends = friends;
console.log( "Friends loaded successfully at", ( new Date() ).getTime() );
}
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide access to the friend repository.
app.factory(
"friendService",
function( $q, $timeout ) {
// Our friend "repository".
var friends = [
{
id: 1,
name: "Tricia"
},
{
id: 2,
name: "Heather"
},
{
id: 3,
name: "Kim"
}
];
// Return the public API.
return({
get: get,
getWithNoop: getWithNoop
});
// ---
// PUBLIC METHODS.
// ---
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function get() {
var deferred = $q.defer();
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function getWithNoop() {
var deferred = $q.defer();
// -- BEGIN: Hack. ----------------------------------------------- //
// CAUTION: This is a work-around for an optimization in the way
// AngularJS implemented $q. When we go to invoke .notify(),
// AngularJS will ignore the event if there are no pending callbacks
// for the event. Since our calling context can't bind to .notify()
// until after we invoke .notify() here (and return the promise),
// AngularJS will ignore it. However, if we bind a No-Op (no
// operation) function to the .notify() event, AngularJS will
// schedule a flushing of the deferred queue in the "next tick,"
// which will give the calling context time to bind to .notify().
deferred.promise.then( null, null, angular.noop );
// -- END: Hack. ------------------------------------------------- //
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
}
);
</script>
</body>
</html>
As you can see, the controller binds the same handler to the "resolve" and "notify" event of the promise. In this way, it can handle the cached data and the live data uniformly. The only difference is in which service-layer method it invokes - get() vs. getWithNoop(). And, if we invoke .get() a few times and then .getWithNoop() a few times, we can see the difference in the console.
I tried to reproduce your problem here. It seems, that you cannot call notifyon the promise directly, but have to wrap into an $applycall.
See also the documentation for $q here.
To quote the exact lines from the example:
since this fn executes async in a future turn of the event loop, we need to wrap our code into an $apply call so that the model changes are properly observed.
You could try this your self and change your code a little bit:
deferred.notify(response); // should not work
resource.query(function (success) {
deferred.notify('Returning from resource'); // should work
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
deferred.notify('caught error!'); //should also work
console.log("error occurred");
deferred.reject(response);
});
I have a controller like this:
function MyCtrl($scope) {
$scope.doSomething = function(){
alert("Do something!");
}
}
And I have multiple views which depend on this (ie multiple of the below):
<div ng-controller="MyCtrl">
...
</div>
The problem is, the data the controller depends on needs to be loaded in the background (the controller does not load that data), and a callback (dataIsReady()) will be called after the data is ready.
function dataIsReady(){
// TODO: call the doSomething() function
}
Now, I want to basically call the doSomething() function, which is inside MyCtrl, from the dataIsReady() function. How can I do that?
I think what you need is a data service, which you can then inject into your controller. You can call a function on your data service which will handle the retrieval of the data and return a "promise" which can then be used to trigger your callback function when the data has loaded.
Have a look at the following code which is a slightly modified version from egghead.io:
Plunker Demo (w/ local storage): http://plnkr.co/edit/9w2jTg?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('AvengersService', function ($http) {
var AvengersService = {
getAsyncCast: function () {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get("avengers.json") // or some JSON service
.then(function (response) {
// The 'then' function here is an opportunity to modify the response
// The return value gets picked up by the 'then' in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return AvengersService;
});
myApp.controller('AvengersCtrl', function($scope, AvengersService) {
// Call the async method and then do something when the data is retrieved
AvengersService.getAsyncCast()
.then(function (asyncData) {
// Callback logic that depends on the data goes in here
console.info("Cast pulled async.");
$scope.avengers.cast = asyncData;
});
});
Hope that helps.
Notice: This approach in this answer is terribly wrong, one should not access to the scope of a controller outside of angular, or outside of controller at all. This would also be terribly slow if you try to call it several times. Other than that, it is fine. I am giving this answer because it is also the simplest way. I would never use that kind of code in production, though. The appropriate way is to write a service to communicate with the controller.
Given that you have defined $scope.doSomething in MyCtrl:
var scp = angular.element('[ng-controller="MyCtrl"]').scope();
scp.doSomething();
Will call doSomething method defined in the controller.
I saw some examples of Facebook Login services that were using promises to access FB Graph API.
Example #1:
this.api = function(item) {
var deferred = $q.defer();
if (item) {
facebook.FB.api('/' + item, function (result) {
$rootScope.$apply(function () {
if (angular.isUndefined(result.error)) {
deferred.resolve(result);
} else {
deferred.reject(result.error);
}
});
});
}
return deferred.promise;
}
And services that used "$scope.$digest() // Manual scope evaluation" when got the response
Example #2:
angular.module('HomePageModule', []).factory('facebookConnect', function() {
return new function() {
this.askFacebookForAuthentication = function(fail, success) {
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
}
}
});
function ConnectCtrl(facebookConnect, $scope, $resource) {
$scope.user = {}
$scope.error = null;
$scope.registerWithFacebook = function() {
facebookConnect.askFacebookForAuthentication(
function(reason) { // fail
$scope.error = reason;
}, function(user) { // success
$scope.user = user
$scope.$digest() // Manual scope evaluation
});
}
}
JSFiddle
The questions are:
What is the difference in the examples above?
What are the reasons and cases to use $q service?
And how does it work?
This is not going to be a complete answer to your question, but hopefully this will help you and others when you try to read the documentation on the $q service. It took me a while to understand it.
Let's set aside AngularJS for a moment and just consider the Facebook API calls. Both the API calls use a callback mechanism to notify the caller when the response from Facebook is available:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
This is a standard pattern for handling asynchronous operations in JavaScript and other languages.
One big problem with this pattern arises when you need to perform a sequence of asynchronous operations, where each successive operation depends on the result of the previous operation. That's what this code is doing:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
First it tries to log in, and then only after verifying that the login was successful does it make the request to the Graph API.
Even in this case, which is only chaining together two operations, things start to get messy. The method askFacebookForAuthentication accepts a callback for failure and success, but what happens when FB.login succeeds but FB.api fails? This method always invokes the success callback regardless of the result of the FB.api method.
Now imagine that you're trying to code a robust sequence of three or more asynchronous operations, in a way that properly handles errors at each step and will be legible to anyone else or even to you after a few weeks. Possible, but it's very easy to just keep nesting those callbacks and lose track of errors along the way.
Now, let's set aside the Facebook API for a moment and just consider the Angular Promises API, as implemented by the $q service. The pattern implemented by this service is an attempt to turn asynchronous programming back into something resembling a linear series of simple statements, with the ability to 'throw' an error at any step of the way and handle it at the end, semantically similar to the familiar try/catch block.
Consider this contrived example. Say we have two functions, where the second function consumes the result of the first one:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Now imagine that firstFn and secondFn both take a long time to complete, so we want to process this sequence asynchronously. First we create a new deferred object, which represents a chain of operations:
var deferred = $q.defer();
var promise = deferred.promise;
The promise property represents the eventual result of the chain. If you log a promise immediately after creating it, you'll see that it is just an empty object ({}). Nothing to see yet, move right along.
So far our promise only represents the starting point in the chain. Now let's add our two operations:
promise = promise.then(firstFn).then(secondFn);
The then method adds a step to the chain and then returns a new promise representing the eventual result of the extended chain. You can add as many steps as you like.
So far, we have set up our chain of functions, but nothing has actually happened. You get things started by calling deferred.resolve, specifying the initial value you want to pass to the first actual step in the chain:
deferred.resolve('initial value');
And then...still nothing happens. To ensure that model changes are properly observed, Angular doesn't actually call the first step in the chain until the next time $apply is called:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
So what about error handling? So far we have only specified a success handler at each step in the chain. then also accepts an error handler as an optional second argument. Here's another, longer example of a promise chain, this time with error handling:
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
As you can see in this example, each handler in the chain has the opportunity to divert traffic to the next error handler instead of the next success handler. In most cases you can have a single error handler at the end of the chain, but you can also have intermediate error handlers that attempt recovery.
To quickly return to your examples (and your questions), I'll just say that they represent two different ways to adapt Facebook's callback-oriented API to Angular's way of observing model changes. The first example wraps the API call in a promise, which can be added to a scope and is understood by Angular's templating system. The second takes the more brute-force approach of setting the callback result directly on the scope, and then calling $scope.$digest() to make Angular aware of the change from an external source.
The two examples are not directly comparable, because the first is missing the login step. However, it's generally desirable to encapsulate interactions with external APIs like this in separate services, and deliver the results to controllers as promises. That way you can keep your controllers separate from external concerns, and test them more easily with mock services.
I expected a complex answer that will cover both: why they are used in
general and how to use it in Angular
This is the plunk for angular promises MVP (minimum viable promise): http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview
Source:
(for those too lazy to click on the links)
index.html
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="myModule" ng-controller="HelloCtrl">
<h1>Messages</h1>
<ul>
<li ng-repeat="message in messages">{{ message }}</li>
</ul>
</body>
</html>
app.js
angular.module('myModule', [])
.factory('HelloWorld', function($q, $timeout) {
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(['Hello', 'world']);
}, 2000);
return deferred.promise;
};
return {
getMessages: getMessages
};
})
.controller('HelloCtrl', function($scope, HelloWorld) {
$scope.messages = HelloWorld.getMessages();
});
(I know it doesn't solve your specific Facebook example but I find following snippets useful)
Via: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
Update 28th Feb 2014: As of 1.2.0, promises are no longer resolved by templates.
http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html
(plunker example uses 1.1.5.)
A deferred represents the result of an asynchronic operation. It exposes an interface that can be used for signaling the state and the result of the operation it represents. It also provides a way to get the associated promise instance.
A promise provides an interface for interacting with it’s related deferred, and so, allows for interested parties to get access to the state and the result of the deferred operation.
When creating a deferred, it’s state is pending and it doesn’t have any result. When we resolve() or reject() the deferred, it changes it’s state to resolved or rejected. Still, we can get the associated promise immediately after creating a deferred and even assign interactions with it’s future result. Those interactions will occur only after the deferred rejected or resolved.
use promise within a controller and make sure the data is available or not
var app = angular.module("app",[]);
app.controller("test",function($scope,$q){
var deferred = $q.defer();
deferred.resolve("Hi");
deferred.promise.then(function(data){
console.log(data);
})
});
angular.bootstrap(document,["app"]);
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
</head>
<body>
<h1>Hello Angular</h1>
<div ng-controller="test">
</div>
</body>
</html>