Getting a promise back from an Angular Resource - javascript

I'm using $resource to do some basic CRUD stuff in Angular but I don't think I understand how to use the different resource methods.
As far as I can tell, the "regular" methods - save(), get() etc happen synchronously, and you can pass in a callback function to run on completion if you want to do "classic" JS async calls. The result of each also has a $promise property which returns a promise, if you want to do async that way (I do!).
But there are also $ versions of all the methods except (for reasons I also don't understand) get() and query(). Are these $ methods a shortcut for .$promise? If so, why is there no $get() or $query()?

Yeah, the docs of ngResource are very hard to understand.
Basically you need to differentiate between
class actions (methods of $resource) and
instance actions (methods of instances of $resource)
class actions like
var User = $resource('/user/:userId', {userId:'#id'});
User.get({userId:123}, function(user) {
user.abc = true;
user.$save();
});
have an additional property $promise that is resolved when the data is returned from the $http request. Thats why this can be written as:
User.get({userId:123})
.$promise.then(function(user) {
$scope.user = user;
});
from the docs:
When the data is returned from the server then the object is an
instance of the resource class. The actions save, remove and delete
are available on it as methods with the $ prefix. This allows you to
easily perform CRUD operations (create, read, update, delete) on
server-side data like this:
That's why user.$save(); can be invoked on the instance in the example above.

Related

JavaScript initializing callback parameters down the callback chain

Note: I'm bootstrapping a reactjs app but this is a general JavaScript question.
I have a special module "locationUtils" that I am trying to keep in it's own package, but keeping that code separate is causing an eyesore with callbacks.
When I access one of it's methods I have to send a callback with it that only has one of its parameters initially defined, and in that method I get the other data parameter to initalize the other parameter.
Can I add in undefined parameters later like that in JavaScript, and is it good practice to initial parameters for a callback method as you go down the callback chain in general, or am I making a convoluted newbie mistake?
/********************Module 1******************************/
var bootStrapUI = function(callback) {
locationUtils.findData(findOtherData(callback));
}
//This gets called last to finalize bootstraping
var findOtherData = function(callback,originalFetchedData){
//use originalFetchedData to get more data
//bootStraping program with all rendering data
callback() //sends back a boolean confirming all fetched
}
/**********************Module2**********************************/
var findData = function(findOtherData){
var data = magicGetData();
findOtherData(findOtherData,data);//I initialized a param late here!
}
It's a good Javascript question, callbacks can become a serious hell for the uninitiated, particularly when they are nested and / or the order in which they return is important.
This is where promises come in: they are an essential tool for Javascript development and about to become part of the standard (in EcmaScript 6).
In essence: a promise is an object that is returned from a function with a method (callback) that is called when the asynchronous action (e.g. API call) has been completed. The difference between a promise and a callback is that promises allow you to structure how you handle the callbacks and, importantly, in what order.
I recently wrote a method that had to make 30 api calls with each call dependent on the results of the previous one (this was not a well designed api). Can you imagine trying to do that with callbacks? As it was, I created an array of promises and used jQuery.when() to handle things when all the api calls had completed.
For the moment we need to use a library for promises. jQuery: https://api.jquery.com/jquery.deferred/ is the obvious one but there are various other implementations that do much the same thing.
Update:
The question relates more specifically to the passing of arguments between callbacks and modifying the arguments as execution moves between them. This can be done easily by passing whatever info you need as an argument to your resolve method.
Typically it looks something like this (using jQuery):
var myAsyncMethod = function(info){
var deferred = $.Deferred();
$.getJSON(myUrl,
function(dataFromServer) {
// Do stuff with data
var newData = doSomething(dataFromServer);
deferred.resolve(newData);
});
});
return deferred.promise();
};
// Make initial method call
myAsyncMethod(myInitialData).then(
function(transformedData){
// transformed data from server is returned here.
}
);

Is providing a Promise as a module's export a valid pattern for asynch initialization in Node.js?

I need to write some modules that load data one time and then provide an interface to that data. I'd like to load the data asynchronously. My application already uses promises. Is providing a promise as the result of requiring a module a valid pattern/idiom?
Example Module:
var DB = require('promise-based-db-module');
module.exports =
DB.fetch('foo')
.then(function(foo){
return {
getId: function(){return foo.id;},
getName: function(){return foo.name;}
};
});
Example Usage:
require('./myPromiseModule')
.then(function(dataInterface){
// Use the data
});
UPDATE:
I've used this for a while now and it works great. One thing I've learned, and it's hinted at in the accepted answer, is that it is good to cache the promise itself, and whenever you want to access the data use then. The first time the data is accessed, the code will wait until the promise is resolved. Subsequent usage of then will return the data immediately. e.g.
var cachedPromise = require('./myPromiseModule');
cachedPromise.then(function(dataInterface){
// Use the data
});
...
cachedPromise.then(function(dataInterface){
// Use the data again somewhere else.
});
This seems like a perfectly good interface for a module who's job is to do a one-time fetch of some data.
The data is obtained async so a promise makes sense for that. The goal is to fetch the data just once and then let all places this module gets used just have access to that original data. A promise works great for that too because it's a one-shot device that remembers its state.
Personally, I'm not sure why you need the getId() and getName() methods when you could just offer direct access to the properties, but either can work.
A downside to this interface is that there is no means of requesting a fresh copy of the data (newly loaded from the DB).

AngularJS - Run code after multiple resources load

I want to execute some code once I load resources from my back-end.
I was able to do this by using a callback on ONE resource request like this:
$scope.resrc = Resrc.query({idResource: 1}, function(){
//CODE AFTER RESOURCE IS LOADED
});
But trying to use $q to wait for MULTIPLE resources to load an then execute code is NOT working for me! (As they suggest here https://stackoverflow.com/a/14545803/215945)
$q.all([
$scope.services = Services.query({idResource: 1}),
$scope.brands = Brands.query({idResource: 1})
]).then(function() {
//CODE AFTER RESOURCES ARE LOADED
});
What am I doing wrong?
From Angular documentation
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most case one never has to write a callback function for the action methods.
The key point here is that, the object you are passing is NOT a promise.
A promise is a way to say you are waiting for something to finish first. It is the backbone of ajax, and I would recommend reading up on it if you are unfamiliar.
To make $q work you will need to give the promise instead object or reference instead
From the same documentation
The Resource instances and collection have these additional
properties:
$promise: the promise of the original server interaction that created
this instance or collection.
On success, the promise is resolved with the same resource instance or
collection object, updated with data from server. This makes it easy
to use in resolve section of $routeProvider.when() to defer view
rendering until the resource(s) are loaded.
On failure, the promise is resolved with the http response object,
without the resource property.
$resolved: true after first server interaction is completed (either
with success or rejection), false before that. Knowing if the Resource
has been resolved is useful in data-binding.
So you will need to do something like
$scope.services = Services.query({idResource: 1});
$scope.brands = Brands.query({idResource: 1});
$q.all([
$scope.services.$promise,
$scope.brands.$promise
]).then(function() {
//CODE AFTER RESOURCES ARE LOADED
});
I think this is because the objects that are returned from the calls to $resource are not promises. I have never used this feature but the documentation suggests
$scope.services = Services.query({idResource: 1});
$scope.brands = Brands.query({idResource: 1});
$q.all([
$scope.services.$promise,
$scope.brands.$promise
]).then(function() {
//CODE AFTER RESOURCES ARE LOADED
});

JQuery blocking for an asynch initialization

I'm writing an AngularJS service for a SignalR hub. Here's my factory for the service:
.factory('gameManager', [function () {
$.connection.hub.start();
var manager = $.connection.gameManager;
return manager;
}])
That code would be perfect, except that that .start() call is asynchronous, and the hub has not completed starting by the time the manager is returned. Basically, I want to block until the start is complete before returning the manager. The .start() method returns a Jquery deferred object, which I'm guessing is part of the answer, but I don't know how to use it without a callback function?
Something like the following should do the trick.
app.factory('gameManager', [function () {
return $.connection.hub.start().then(function() {
return $.connection.gameManager;
});
}])
Now your callback function will return a deferred/promise too, so the service consumer will need to be expecting that. Your consuming code might look something like this:
gameManager.then(function(gameManager) {
// do whatever with game manager
gameManager.doSomething();
});
The docs for jquery Deferred are here. In particular, check out Deferred.then().
Note that:
the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function ... These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks...
update:
An alternate approach (and probably the better approach - since it won't require that your consumer handle the promise) is to let the hub initialize completely, before setting up your factory, and kicking off your app controller. Something like this...
$.connection.hub.start().then(function() {
app.factory('gameManager', function() {
return $.connection.gameManager;
});
// ...
// kick off the rest of the app..
});
You will not find what you are looking for, you will have to go with Lee's answer. Javascript is mostly single-threaded and does not allow blocking (with specific exceptions, such as alert window or synchronous ajax call).

How does the $resource `get` function work synchronously in AngularJS?

I was watching this AngularJS tutorial describing how to hook into Twitter with an Angular resource. (Video tutorial) Here is the resource that is set up in the example controller:
$scope.twitter = $resource('http://twitter.com/:action',
{action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
{get: {method: 'JSONP'}});
The tutorial shows that there are a couple ways to get data back from the resource using the get call. The first method is by passing a callback to the get function. The callback will be called with the result after the ajax request returns:
$scope.twitter.get(function(result) {
console.log('This was the result:', result);
});
I understand this method. It makes perfect sense to me. The resource represents a place on the web where you can get data, and get simply makes an ajax call to a url, gets json back, and calls the callback function with the json. The result param is that json.
It makes sense to me because it seems obvious that this is an asynchronous call. That is, under the hood, the ajax call fires, and the code following the call isn't blocked, it continues to be executed. Then at some indeterminate point later on, when the xhr is successful, the callback function is called.
Then the tutorial shows a different method that looks a lot simpler, but I don't understand how it works:
$scope.twitterResult = $scope.twitter.get();
I assume that the xhr underneath get must be asynchronous, yet in this line we are assigning the return value of the get call to a variable, as if it returned synchronously.
Am I wrong for not understanding this? How is that possible? I think it's really neat that it works, I just don't get it.
I understand that get can return something while the xhr underneath it goes off and processes asynchronously, but if you follow the code example yourself, you will see that $scope.twitterResult gets the actual twitter content before any subsequent lines are executed. For example, if you write console.log($scope.twitterResult) immediately after that line, you will see the results from twitter logged in the console, not a temporary value that is replaced later on.
More importantly, because this is possible, how can I write an Angular service that takes advantage of this same functionality? Besides ajax requests, there are other types of data stores requiring asynchronous calls that can be used in JavaScript which I would love to be able to write code for synchronously in this style. For example, IndexedDB. If I could wrap my head around how Angular's built-in resources are doing it, I would give it a shot.
$resource is not synchronous although this syntax might suggest that it is:
$scope.twitterResult = $scope.twitter.get();
What is going on here is that call to the AngularJS will, after call to twitter.get(), return immediately with the result being an empty array. Then, when the async call is finished and real data arrives from the server, the array will get updated with data. AngularJS will simply keep a reference to an array returned and will fill it in when data are available.
Here is the fragment of $resource implementation where the "magic" happens: https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L372
This is described in the $resource documentation as well:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most case one never has to write a callback function for the action methods.
$q can do this trick too. You can convert a normal object to a 'delayed value' using something like this:
var delayedValue = function($scope, deferred, value) {
setTimeout(function() {
$scope.$apply(function () {
deferred.resolve(value);
});
}, 1000);
return deferred.promise;
};
and then use it in a controller, to get a similar effect to what $scope.twitter.get() does in the OP's example
angular.module('someApp', [])
.controller('someController', ['$scope', '$q', function($scope, $q) {
var deferred = $q.defer();
$scope.numbers = delayedValue($scope, deferred, ['some', 'numbers']);
}]);

Categories