Angularjs promise blocking solutions - javascript

I need a function which will return a boolean value. The first time this function is called, it should load the value from an async service (that returns a promise) and then cache it. Subsequent calls to this function should return the cached value. Problem is the caller of this function should be blocked until it gets a value. Whats the best solution in this case? Should the function return a promise either way (i.e whether its cached or not), and just put everything that needs to be blocked inside promise.then(). And if thats a good solution, how do I return a promise in either case (e.g. when its cached or not)?
Below is some code to better illustrate the problem:
function isSomethingStored() {
if(getFromBrowserStorage() != null) {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
}
var promise = ThirdParty.getSomethingThroughAjax();
promise.then(function(theSomething) {
addToBrowserStorage(theSomething);
return true;
});
return promise;
}
function caller() {
var promise = isSomethingStored();
promise.then(function(isReady) {
//we can continue
});
}

Yes - it should always return a promise unless there is another consideration preventing it from doing so. This is for consistency - if a method might be asynchronous it is best practice to always treat it as asynchronous so you won't get funky race conditions later on.
Since you can't block anyway, and the workarounds are hacky - I think it probably fits your problem optimally anyway. Simply put all reliant code in a then.

Related

Purpose of this custom resolve function?

I'm having trouble puzzling this resolve function out...
function _resolve(){
var $deferred = $.Deferred();
return $deferred.resolve.apply($deferred, arguments).promise();
}
I see it being used like
if (availableLanguages.length === 1) {
return _resolve(availableLanguages[0]);
}
and
if (detectedLocale && availableLanguages.indexOf(detectedLocale) > -1) {
// save the locale so the user does not get asked for it again
return _resolve(detectedLocale, true);
}
The _resolve function is a shortcut for creating kind of a dummy jQuery Deferred object, which is initially resolved. They should have rather called it for example _createResolvedPromise which would be more intuitive, but we always used to save some characters don't we.
In general this technique is needed when you have a function which returns a deferred, but in some cases you can have an early return with some value. In this case you cannot just return the value, because the caller expects a deferred, so you have to create a deferred object and immediately resolve it with that value. You can perhaps call these deferred objects constant deferred objects or so.
The apply is used to call the resolve with the optional arguments passed to _resolve. Those arguments will be passed to the done callback later on. See the documentation here.
Calling promise() on the deferred object is nothing more than wrapping it into a promise object, so that the consumer cannot call resolve for example. See the documentation here.
For example, let's assume we want to retrieve something via ajax, but also we cache the results.
var _cachedResult;
function getResults() {
if (_cachedResult) {
// for THIS line they made the _resolve shortcut, with which I could write _resolve(_cachedResult)
return $.Deferred().resolve(_cachedResult).promise();
}
return $.ajax(...).done(function(result) {
_cachedResult = result;
});
}
The caller can use it like this.
getResult().done(function(result) {
// do something with result, which maybe came from the cache
});
Hope it makes sense.
In short, it appears to be wrapping up various calls in a Promise/async fashion.
It's likely that detectLocale is an async function, and they're using _resolve to simply handle it in a synchronous manner. Your _resolve function is returning a Promise, which presumably the consumers of those return values are using.
I would expect to see something like this if you trace it up:
function getLanguages() {
if (availableLanguages.length === 1) {
return _resolve(availableLanguages[0]);
}
return new Promise(); // or something, unless it's checking for null outside
}
function doSomething() {
getLanguages().then(languages => console.log(languages));
}

Return deferred object or null

I have a function that submits some data. Depending on the state of some boolean, I might not want to submit this data. However, the caller is always expecting a deferred object as the return value to check .done() or .fail(). It seems you cannot return null or return nothing when something expects done/fail (makes sense), so I don't know how to return from this async call.
I can hack in a $.Deferred object and immediately resolve/return it, but that seems like bad design. I can't change the calling method here.
How can I return a deferred object or some other return value that satisfies .done() or .fail() without using a deferred object?
function foo(a) {
bar(a).done(function () {
console.log("done");
}).fail(function () {
console.log("fail");
});
}
function bar(a) {
if (a){
// Could create a $.Deferred and resolve it
return;
}
return doAsync();
}
function doAsync() {
var defer = $.Deferred();
defer.resolve();
return defer.promise();
}
foo(false); // fine
foo(true); // error
http://jsfiddle.net/ma4grjj4/2/
The main problem is that it's usually a design smell to have boolean arguments that changes the process flow of a function.
I think you will agree that doing something like the following makes no sense:
function process(doNotProcess) {
if (doNotProcess) return;
//process
}
In the above example, it would make more sense to simply not call on process() to avoid processing.
"What I would do ideally is in foo skip the entire call of bar, but a
lot of stuff happens inside of bar().done that needs to occur in both
cases"
That code should probably be factored out of the done callback and put in a different function, which would allow you to reuse it without having to call bar.
"I can hack in a $.Deferred object and immediately resolve/return it,
but that seems like bad design."
Creating deferred objects and resolving them right away is a quite standard approach when you need to standardize the use of an API that may have a synchronous or asynchronous implementation.
It's a very good practice to do so because it frees the client from having to rely on implementation details.

Angular/Promises - Multiple Controllers waiting for the same $promise? [duplicate]

I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)

Caching a promise object in AngularJS service

I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)

Return either local variable or GET results

I would like to return x || $.get.
Or in other words, if x is true, then return x, else perform a GET call and return the value provided by the server.
My attempt is listed below (ideally, it would follow the return x || y format maybe with an anonymous function? instead of the if/then).
Problem is my return from my $.get function appears not to be what I expected.
Would appreciate an explanation of what is going on.
Thanks
$(function(){
function test(x,y) {
if(x==true) {return true;}
else{
//test.php is echo($_GET['y']==123);
$.get('ajax.php',{'y':y},function (status) {return status;});
}
}
alert(test(false,123));
});
If you're using jQuery 1.5 or later, Deferred and Promise are your friend for this kind of thing. Any time you call AJAX calls what you get back are Promise objects which you can attach functions to via .done(), .fail(), and .then().
However! As pointed out by this excellent intro to deferred/promise and all this stuff (http://www.erichynds.com/jquery/using-deferreds-in-jquery/), you can also use $.wait()'s ability to handle a value that isn't a promise to automatically do caching. So code like this:
$.when(getToken()).done(
function (token) {
// do something with the token, which may or may not have been
// retrieved from the remote service
}
);
Can handle getting either a cached value back or a promise with no problem:
function getToken() {
// Return either the cached value or a jQuery Promise. If $.when() gets the
// cached value it will immediately realize that you didn't give it a
// promise and it will instead create a jQuery Deferred to return and
// .resolve() it using the value it did get. Thus, either way what
// comes out of the function is something .when() can deal with and call a function.
if (this.cache["token"]) {
return this.cache["token"];
} else {
return $.get(" ... some url ... ");
}
};

Categories