Should I emulate '.$promise' objects? - javascript

I am working on an admin app with AngularJS. The app fetches its data from the server using $resource. I end up with data objects containing '$promise' property to determine when the data has been fetched.
Everything's fine.
Now, this admin app can also create new objects. Those new objects are managed by the same controllers than the one that usually come from '$resource'.
So now I have 2 kind of objects:
Objects with $promise property. I should use $promise.then() before manipulating them with full data
Plain objects. They don't have a $promise property, their value is accessible instantly
I would like to reduce code and to have a single use-case, not having to check if object data is resolved, or if it is not a promise.
Is there any problem in building my 'plain' objects by adding them a '$promise' property which is already resolved to themselves? That way, I would always use 'myObject.$promise.then()'.
Is there any common pattern to handle this situation? I could not find any 'standard' method to create this kind of objects with Angular.

You could use $q.when if unsure whether the object has a promise or not.
(obj.$promise ? obj.$promise || $q.when(objResult)).then(function(result){
//handle success case.
//Incase of object not having the $promise property result will be object itself
})
if the resultant property does not have a promise this will resolve with promise.
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
You do not need to always create a promise and attach it on the data that is being transferred, instead you could make your methods returns promise thus help you implement promise pattern and abstract out the promise logic on your service itself. Example:-
function getCurrentUserData(userId){
var defered = $q.defer();
...
//Check in my cache if this is already there, then get it from cache and resolve it
defered.resolve(objectFromCache);
//my else condition
//It is not in cache so let me make the call.
$http.get('myurl').then(function(result){
//Do validation with data and if it doesnot match then reject it
defered.reject(reason);
//Else Do something with the data put it into the cache, mapping logic etc..
defered.resolve(dto);
}).catch(function(error){
//do something with error and then reject
defered.reject(reasonDerived);
});
return defered.promise;
}
Here is a simplified and less explicit version (Credit: Benjamin Gruenbaum):
var cached = null;
function getCurrentUserData(userId){
return cached = cached || $http.get('myurl').then(function(result){
if(isNotValid(result)) return $q.reject(reason); // alternatively `throw`
return transformToDto(result);
}, function(response){
if(checkforsomethingonstatusandreject)
return $q.reject('Errored Out')
//do some actions to notify the error scenarios and return default actions
return someDefaults; });
}
You can of course return $q.reject(derivedReason) here rather than returning the reason and transform it based on further checks, the idea is caching the promise rather than the value. This also has the advantage of not making multiple http requests if the method is called before it returns once.
Now you could always do:-
getCurrentUserData(userid).then(function(user){
}).catch(function(error){
});
Promises can be chained through as well. So you could also do this:-
return $resource('myresource').$promise.then(function(result){
//Do some mapping and return mapped data
return mappedResult;
});

A+ promises should offer a static method:
Promise.resolve(val);
...which generates a pre-resolved promise. You should return one of these if your promises library offers this. Great. I do this frequently to avoid interface duplication.

Related

Resolve new data in already resolved promise

I have a single page app in angular. There is a global available state available through a context service.
This context service has a get and set method. The get method is a promise since the context is not yet set up once the page is loaded but is gotten through an API.
Once the context is set-up the promise is resolved and I can access my context through the get method.
However how can I deal with the set method. It is possible to change the context with the set method. But since the promise is already resolved the get method will return the old context.
Is it possible to 'substitute' the data the promise returns on a then call after it has been resolved?
Thanks!
The get method is a promise...
That doesn't make much sense. I'm assuming it returns a promise.
However how can I deal with the set method. It is possible to change the context with the set method. But since the promise is allready resolved the get method will return the old context.
Code shouldn't keep and reuse the old promise like that. E.g., you're suggesting:
var p = object.getThePromise();
p.then(function(value) {
// ...use the value...
});
// later - this is the bit that's wrong
p.then(function(value) {
// ...use the value...
});
It shouldn't do that. It should go back to getThePromise if it wants the value again later (if it wants a new value).
Is it possible to 'substitute' the data the promise returns on a then call after it has been resolved?
No. An important part of the promise contract is that a promise is only ever settled once, and the settled value does not change.
It's hard to say without seeing your API, but if the API gives the impression you can call the "get" and then reuse the resulting promise, it would be best to change the API to make it no longer give that impression. But again, without API specifics, it's hard to say whether that's the case or suggest a change.

Chaining ngResource promises

Im using a REST Api that provides non-nested resources API. That leads to chained calls, that i want to make with promises. Im using the ngResource from angular, and im having problem chaining the calls. The idea is first to get the description of an active element. Here i ask for an JSON, response looks like this :
{id : 0, block : [0,3,4]}
After i got this information, i try to get the data about blocks. The implementation looks like this:
Element.get({'id':state.elementID}).$promise.then( function(element) {
// Element hast block entry with array of belonging blockIDs
angular.forEach(element.block, function(blockId){
// Get all the Blocks, that have the defined ID ( Foreign key)
return Block.get({'id':blockId}).$promise;
});
}).then(function(block){
// Create an element and ADD it to the model
var uiElem = new UIElem(block.id, "#",block.name, "block");
$scope.list.push(uiElem);
angular.forEach(block.parameter, function(element){
/// Chain other calls...
.......
});
})
The problem that the second then gets undefined block, although the GET call get a correct JSON from the server.
Im wondering if i am using the chaining of the promises incorrectly or im using the elements wrong
You are not correctly chaining your promises. For each block you sending another request to the server immediatly.
Use $q.all for chaining:
// Element hast block entry with array of belonging blockIDs
return $q.all(element.block.map(function(blockId){
// Get all the Blocks, that have the defined ID ( Foreign key)
return Block.get({'id':blockId}).$promise;
}));
This should give you the array of resulting blocks here:
}).then(function(blocks){...
The chained promises use the previous promise's result as an input. You have no return in your first promise, thus the second receiving undefined as its block.
You should return element or whatever is relevant in your first promise.
This is described in the $q documentation.

ES6 Promise automatic post-processing / cloning results

I have a situation where I am building a data layer based on ES6 JS Promises that fetch data from the network. I am caching all Promises internally by the url.
Everything seems to be working fine except one thing. I want to ensure that the data coming out of the network layer is a copy/clone of the data retrieved from the network and I obviously do not want to do that everywhere in the client code that implements Promise's then handlers.
I would like to set this up so then handler automatically gets a copy of the cached data.
To add a twist to this, I would like this to be configurable on a url basis inside the data layer so that some Promises do the extra post-processing copy while others return just the raw result.
Can anyone suggest a proper implementation to accomplish this? I should mention that I would like to get a new copy of the original raw result each time a new client asks for it.
The current simplified pseudo implementation looks like this
getCachedData(url){
if (cache[url]) {
return cache[url];
} else {
var promise = new Promise(function(resolve,reject){
var data = ...ajax get...;
resolve(data);
});
cache[url] = promise;
}
getCachedData(url).then(result=>{
here I want the result to be a copy of data I resolved the original promise with.
});
Structure it like this:
function retrieveCopiedData () {
// getDataFromServer is your original Promise
return getDataFromServer().then(function (value) {
// use a library of your choice for copying the object.
return copy(value);
})}
}
This means that all consumers of retrieveCopiedData will receive the value returned from retrieveCopiedData's then() handler.
retrieveCopiedData().then(function (value) {
// value is the copy returned from retrieveCopiedData's then handler
})
You can add conditional logic to retrieveCopiedData as you see fit.
It seems like you just want to incorporate the cloning process right in your data layer:
getCachedData(url){
if (!cache[url]) {
cache[url] = new Promise(function(resolve,reject){
var data = ...ajax get...;
resolve(data);
});
}
if (requiresPostProcessing(url))
return cache[url].then(clone);
else
return cache[url];
}
Notice that it might be a good idea not to clone the data each time it is retrieved, but to simply freeze the object that your promise is resolved with.

JavaScript native Promise execute callback on both results

Is there any way to execute callback on both results of Promise object?
For example I want to make some cleanup logic after execution of xhr request. So I need to do something like this:
var cleanUp = function() { something.here(); }
myLib.makeXhr().then(cleanUp,cleanUp);
In jquery Defered for example i can use method always():
myLib.makeXhr().always(function() { something.here(); });
Does Promise support something like this?
No, there is none. It was discussed but the spec is minimal. It doesn't include a bunch of other functionality. It's designed to interoperate well with library promises and to provide simple functionality.
Here is a correct polyfill of that proposal originally made by StefPanner.
Moreover, I disagree with the current now deleted answers adding it themselves because they're all doing it wrong (as an enumerable property - no fun). Even if we ignore what it does to the return values and error state of the returned promise. The intended way to extend native promises is by subclassing them, sadly, no browsers support this yet so we'll have to wait.
Instead of messing with native prototypes, we should use a different pattern:
openDb().then(foo).then(bar).finally(close).then(more);
Is susceptible to us forgetting to call close, even if we open it 100 times in our app, forgetting to close it even once can still be devastating. On the other hand - we can use the disposer pattern which some promise libraries provide built in ourselves:
openDb(function(db){
return foo(db).then(bar);// chain here
}).then(more);
Basically - this pattern means instead of having openDB return a promise - we have it take a function and return a promise, when the function is run, if it returns a promise we wait for that promise to resolve. It looks something like:
function openDb(withDb){
return justOpenWithoutCleanUp().
then(withDb).
then(clean, function(e){ clean(); throw e; }); // note the rethrow
}
Promise object supports 'always'.
For eg:
var oPromise= jQuery.ajax({
url:YOUR_URL
}).always(function(){
YOUR_CLEAN_UP_METHOD();
})

jQuery deferred: "then" ... "when" ... where's the "join" or "blockUntilDone"?

I'm using the JayData.js library. It works quite well. However, I have a few situations where I've got a toArray() call deep in the function tree. Rather than trying to access my "busy" signal from there, I'd just as soon have the method block. Is that possible? I'm picturing something like "context.Groups.toArray(myObservableVar).block()".
Update 1: It appears that the JayData library returns a jQuery deferred object judging from the use of "then" and "when" operators on the return value. Is there a corresponding method to "join" -- meaning wait for the finish?
Indeed JayData toArray() (and all relevant data returning or saving/updating method) implements jQuery deferred. As from 1.0.5 you have to include the JayDataModules/deferred.js in order to this functionality to work.
For your use case $.when might be an answer:
var customers = context.Customers.toArray();
var products = context.Products.toArray();
var suppliers = context.Suppliers.toArray();
$.when(customers, products, suppliers).then(function(customers, products, suppliers) {
//we have everything here
//notice the the parameter names are shadowed as the var customers variable only
//holds a promise not the value
customers.forEach( ... );
products[12].ProductName = "X";
});
A blockUntilDone() method would go against the principles of deferred execution and continuations. JayData's toArray() is asynchronous because it is designed not to block the caller.
If you want this kind of code:
// Initialize context and groups...
var arrayOfGroups = context.Groups.toArray(); // Want synchronous behavior.
// Do something with 'arrayOfGroups'...
Trying to block until the deferred is resolved is not the solution. Move the last part of your code into a callback passed to toArray() instead:
// Initialize context and groups...
context.Groups.toArray(function(arrayOfGroups) {
// Do something with 'arrayOfGroups'...
});
Alternatively, bind to the returned promise with done() or then():
context.Groups.toArray().done(function(arrayOfGroups) {
// Do something with 'arrayOfGroups'...
});

Categories