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)
Related
So, I'm having a problem. I need to make potentially hundreds of http calls and they must be done one after the other. I also need to be able to interrupt the whole process at any time.
The solution I'm using right now is this one, but it doesn't allow me to properly interrupt the "chain" (I use jQuery and Deferreds because I haven't learned to replace them with Promises yet):
function doAnHttpCall() {
return $.post('http://the.domain/theendpoint', { theParam: this });
}
var anArrayOfParams = ['param1', 'param2', 'param3'];
var p = $.Deferred();
setTimeout(p.resolve.bind(p), 0);
var promise = anArrayOfParams.reduce(function(prev, cur) {
return prev.then(doAnHttpCall.bind(cur));
}, p)
.done(function() {
console.log('all done.');
});
(I've found this solution here)
The problem here is that the only way to sort-of break out of the reduce function is by modifying the array you're looping through and check for a particular value and always return immediately when you find it, instead of executing the "doAnHttpCall" method (in this case). This would still make me loop over potentially hundreds of elements of the array instead of just interrupting the process, which is very ugly.
There must be a better way to do this. Or do I really need to use a function that calls itself with the next element to process when an http call has finished? It sounds "bad-practice-y".
Thanks for the help.
You would use async/await:
const request = param => $.post('http://the.domain/theendpoint', { param });
(async () => {
for(const param of ['param1', 'param2', 'param3']) {
let result = await request(param);
if (result === 'particular value') break;
}
console.log('all done!');
})();
I've created this object which contains an array, which serves as a work queue.
It kind of works like this:
var work1 = new Work();
var work2 = new Work();
var queue = Workqueue.instance();
queue.add(work1) // Bluebird promise.
.then(function addWork2() {
return queue.add(work2);
})
.then(function toCommit() {
return queue.commit();
})
.then(function done(results) {
// obtain results here.
})
.catch(function(err){});
It works in that case and I can commit more than one task before I call the commit.
However if it's like this:
var work1 = new Work();
var work2 = new Work();
var queue = Workqueue.instance();
queue.add(work1)
.then(function toCommit1() {
return queue.commit();
})
.then(function done1(result1) {
// obtain result1 here.
})
.catch(function(err){});
queue.add(work2)
.then(function toCommit2() {
return queue.commit();
})
.then(function done2(result2) {
// obtain result2 here.
})
.catch(function(err){});
Something may go wrong, because if the first commit is called after the second commit (two works/tasks are already added), the first commit handler expects a result but they all go to the second commit handler.
The task involves Web SQL database read and may also involves network access. So it's basically a complicated procedure so the above described problem may surface. If only I can have a addWorkAndCommit() implemented which wraps the add and commit together, but still there is no guarantee because addWorkAndCommit() cannot be "atomic" in a sense because they involves asynchronous calls. So even two calls to addWorkAndCommit() may fail. (I don't know how to describe it other than by "atomic", since JavaScript is single-threaded, but this issue crops up).
What can I do?
The problem is that there is a commit() but no notion of a transaction, so you cannot explicitly have two isolated transactions running in parallel. From my understanding the Javascript Workqueue is a proxy for a remote queue and the calls to add() and commit() map directly to some kind of remote procedure calls having a similar interface without transactions. I also understand that you would not care if the second add() actually happened after the first commit(), you just want to write two simple subsequent addWorkAndCommit() statements without synchronizing the underlying calls in client code.
What you can do is write a wrapper around the local Workqueue (or alter it directly if it is your code), so that each update of the queue creates a new transaction and a commit() always refers to one such transaction. The wrapper then delays new updates until all previous transactions are committed (or rolled back).
Adopting Benjamin Gruenbaum's recommendation to use a disposer pattern, here is one, written as an adapter method for Workqueue.instance() :
Workqueue.transaction = function (work) { // `work` is a function
var queue = this.instance();
return Promise.resolve(work(queue)) // `Promise.resolve()` avoids an error if `work()` doesn't return a promise.
.then(function() {
return queue.commit();
});
}
Now you can write :
// if the order mattters,
// then add promises sequentially.
Workqueue.transaction(function(queue) {
var work1 = new Work();
var work2 = new Work();
return queue.add(work1)
.then(function() {
return queue.add(work2);
});
});
// if the order doesn't mattter,
// add promises in parallel.
Workqueue.transaction(function(queue) {
var work1 = new Work();
var work2 = new Work();
var promise1 = queue.add(work1);
var promise2 = queue.add(work2);
return Promise.all(promise1, promise2);
});
// you can even pass `queue` around
Workqueue.transaction(function(queue) {
var work1 = new Work();
var promise1 = queue.add(work1);
var promise2 = myCleverObject.doLotsOfAsyncStuff(queue);
return Promise.all(promise1, promise2);
});
In practice, an error handler should be included like this - Workqueue.transaction(function() {...}).catch(errorHandler);
Whatever you write, all you need to do is ensure that the callback function returns a promise that is an aggregate of all the component asynchronisms (component promises). When the aggregate promise resolves, the disposer will ensure that the transaction is committed.
As with all disposers, this one doesn't do anything you can't do without it. However it :
serves as a reminder of what you are doing by providing a named .transaction() method,
enforces the notion of a single transaction by constraining a Workqueue.instance() to one commit.
If for any reason you should ever need to do two or more commits on the same queue (why?), then you can always revert to calling Workqueue.instance() directly.
My service needs to retrieve a value asynchronously, but once I have it, I'd like to used a cached version of the value.
When two controllers call this service, I'd expect the first one to cache the retrieved value and the second one to use the cached value, but according to the log, I never find a cached value. When this runs, I see a log message that shows the value being cached, then, when I follow an angular route to a different controller, I do not see that the service finds the cached value. Why does it not run according to my expectation**?**
angular.module('myApp.services').factory('Config', function() {
var Config = { };
Config.currentYear = function() {
if (Config._currentYear) {
// sadly, we never execute here
console.log("returning cached year");
return Parse.Promise.as(Config._currentYear);
}
return Parse.Config.get().then(function(config) {
console.log("caching year");
Config._currentYear = config.get("currentYear");
return Config._currentYear;
});
};
return Config;
});
A couple notes: (1) I named the cached attribute _currentYear, adding the underscore to avoid colliding with the function name. Not sure if I need to do that. (2) I return a fulfilled promise when the value is cached, so the function always returns a promise...also not sure if that's needed, but figure it can't hurt.
Instead of caching the data, why don't you just cache the promise and return it. When you cache the data, you are setting the data Config._currentYear only within the success callback and there are chances that other subsequent call(s) happening before the success callback is run. So you end up making the same call again. You can easily see this when you have calls made to the same service method from different controllers which are instantiated, by their presence on the same template. Caching a promise upfront will avoid these issues.
angular.module('myApp.services').factory('Config', function() {
var config; //Just use to save the promise
Config.currentYear = function() {
/*If there is already a call made before return the promise else
make the actual call and store the promise in the variable.*/
return config || config = Parse.Config.get().then(function(config) {
return config.get("currentYear");
});
};
});
tl;dr : I'm looking for a way to have the first .then callback make changes to the data that is passed to subsequent chained events.
I have a library that encapsulates some async operations.
dudetools.getDude(2); // causes an XHR against REST resource "Dude" for row id 2
For awesomeness purposes, dudetools.getDude returns the promise created by the underlying $.ajax call. Thus, I can do things like:
dudetools.getDude(dudeId).done(function(dudeData) { /* do stuff with dude's data */ });
Now I'm trying to modify dudetools so that it'll do some convenient data-massaging on response data before continuing along the promise chain. I want this massage to happen universally, without calling code having to request it or even know about it.
Because the dudetools implementation can't share a closure with all calling code, I'm hoping to leverage the fact that, in JavaScript, non-scalars are always passed by reference rather than by value.
Consider:
var urStuff = {};
function wreck(x) {
x.isWrecked = 'so wrecked';
}
wreck(urStuff);
// urStuff.isWrecked === 'so wrecked' ^.^
I dare you to try it.
So, I was hoping this would work:
dudetools = {
'getDude': function(dudeId) {
return $.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
// I'm so clever!
dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
});
}
}
Of course, it doesn't work. My clever code is being executed (I've seen it), and it's reaching the right conclusions, but subsequent Deferred events in the chain never see the modifications. I.e.:
$.when(
dudetools.getDude(dudeId)
).done(function(mysteriouslyUnmodifiedInfo) {
/* the info passed to this function is mysteriously unmodified! HALP */
});
Any suggestions? Is there a way to accomplish what I'm after?
Also: I'm still kind of new to promises in general, and my grasp of the differences between Deferreds, Promises, and associated constructs is still kind of fuzzy, so I'd very much appreciate your efforts to be clear and explicit when explaining to me how I've ruined everything.
Thanks very much.
EDIT: updated to reflect fact that dudetools.getDude returns a promise, not a Deferred. Because I now (mostly) understand the difference.
The magic of .then is that it pipes its return value into the next callbacks param.
If you don't return your object (even if you haven't changed anything), then undefined is returned by default.
do_something()
.then(function (json) { return JSON.parse(json); })
.then(function (response) { return response.data; })
.then(function (data) { data.tweaked = true; return data; });
You'll want to return your own new Deferred.promise() object.
http://api.jquery.com/deferred.promise/
dudetools = {
'getDude': function(dudeId) {
var dfd = new jQuery.Deferred();
$.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
// I'm so clever!
dfd.resolve(dudeData);
});
return dfd.promise();
}
}
Hope that helps.
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)