I am refactoring a resource-loading function that used a traditional callback pattern to instead use jQuery Deferreds.
This function takes and array of urls, creates a new Deferred object for each resource, creates a $.when Deferred object to watch them, and returns the promise of the $.when object.
Here's a simplified version of the method:
theLib = {
getResources : function(paths) {
var deferreds = [];
theLib.isLoading = true;
$.each(paths, function(i, path) {
// do something with the path, either using getScript
// or making a new $.Deferred as needed
deferreds.push(/* the deferred object from above comment */);
});
theLib.currentDeferred = $.when.apply(null,deferreds).always(function() {
theLib.isLoading = false;
});
return theLib.currentDeferred.promise();
};
This works well.
My problem: In the old script, not only would one call theLib.getResources() based on user actions or events, but one would also define a master list of resources that the application will "stream" while the user is not taking any action (i.e. reading an article).
Some of these streamed resources would be the same resources that could be called manually when a user does take action. The script was smart enough not to load a resource twice by keeping track of what was loaded.
It also keeps track of theLib.isLoading. The beginning of that function looked something like this:
getResources : function(paths, callback) {
if (theLib.isLoading) {
settimeout(function() {
theLib.getResources(paths, callback);
}, 100);
}
theLib.isLoading = true;
I can't do this anymore, because I need to return a promise object.
I know that I can check theLib.currentDeferred.isResolved(). At that point, if it is not resolved: How can I add more deferred objects to the $.when queue that is being watched?
I guess I needed to ask the question to find a solution for myself. Basically I added the following code the beginning of getResources:
if ( ! theLib.currentDeferred.isResolved()) {
return $.when(theLib.currentDeferred).always(function() {
theLib.getResources(paths);
}).promise();
}
The above was failing. The correct solution was to pipe the results:
if ( ! theLib.currentDeferred.isResolved()) {
return theLib.currentDeferred.pipe(function() {
return theLib.getResources(paths);
});
}
Related
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)
Is such pattern possible in jQuery or javascript?:
$.when(function(){
//I init many plugins here, some of them use ajax etc but I dont really control it
//I only do something like $(div).somePlugin() here
$("div").myPlugin()
}).done(function(){
//and this part I want to be executed when all ajaxes and deferred stuff from when part is done
//however I cannot go to every plugin and add something like deferred.resolve() etc.
});
and myPlugin would have for example
$.fn.myPlugin = function(){
$(this).load(someUrl);
};
(but I cannot change myPlugin as its some external code.)
Basically I've got a lot of stuff happening and a lot of this uses async. functions. I want to execute some function when all this async. stuff is done, but I cannot change plugins code so I can't add .resolve() stuff to it.
Yes, this is basically what .when does!
// changes body html
var fisrtApi = $.get("http://something/foo").then(function(r){ $("body div").html(r); });
// inits some API for usage
var secondApi = somePromiseReturningFucntion();
// sets a plugin on top of a page
var somePlugin = someOtherPromiseReturningFn();
$.when(firstApi,secondApi,somePlugin).done(function(r1, r2, r3){
// all of them ready, results are the arguments
});
It is also pretty straightforward to convert a regular non promise returning API to promises.
For example, let's do $(document).ready(function(){
// returns a promise on the document being ready
function whenDocumentReady(){
var d = $.Deferred();
$(document).ready(function(){ d.resolve(); });
return d.promise();
};
Which would let you do:
$.when($.get("http://yourAPI"), whenDocumentReady()).done(function(apiResult,_){
// access API here, the document is also ready.
});
For example - with jQuery twitter, the library provides a callback for when it's done fetching data. You would promisify it:
function getTweets(username, limit){
var d = $.Deferred();
$.twitter(username, limit , function(res){ d.resolve(res); });
return d.promise();
}
Which would let you do:
$.when(getTweets("someusername"),whenDocumentReady()).done(function(tweets){
// document is ready here _and_ the twitter data is available,
// you can access it in the `tweets` parameter
});
If that is what you are looking for, then yes, it is totally possible
$.when(sync(), async(), ajax()).done(function(s,a1, a2) {
console.log( s + ' + ' + a1 + ' + ' + a2) // outputs sync + async + ajax
})
function sync() {
return 'sync'
}
function async() {
var d = $.Deferred();
setTimeout(function() {
d.resolve('async')
}, 100)
return d;
}
function ajax() {
return $.post('http://jsfiddle.net/echo/html/', { html: 'ajax' })
}
I guess the only way to do it is kind of ugly.
If you cannot use deferreds and resolve method, you have no other choice than listen to changes in the dom or context (plugins usually modify the DOM or create new object in the context).
Then you will have to look for $(myElt).hasClass('<class_created_and_applied_by_my_plugin>') turning from false to true, or stuff like this.
You have to create a deferred for each plugin and wrap the previous test in a setInterval to simulate a listener, and finally resolve the deferred.
This way, you can put all your deferred into a when and be sure they are all resolved before going on.
But this is really really uggly cause you have to personalize the test for each plugin.
And I guess, this will certainly slow down the browser too.
I have looked at the example posted here: YDN-DB with multiple deferred which contains some code that is very close to what I want, but not quite.
I am wondering if it is safe to nest deferred queries in a transaction? For example:
loadWorkOrders: function() {
var params = {
userId: 1,
status: Status.Allocated
};
var allOrders = null;
return workOrderHttpService.getWorkOrders(params).then(function(orders) {
allOrders = orders.data;
return ydndatabase.open();
}).then(function(db){
return db.run(function(runDb){
allOrders.forEach(function(workOrder){
runDb.count(Store.WorkOrder, ydn.db.KeyRange.only(workOrder.id)).then(function(count) {
if(count == 0) {
return runDb.put(Store.WorkOrder, workOrder);
} else {
return workOrder;
}
});
});
}, [Store.WorkOrder], TransactionType.ReadWrite)
});
}
EDIT: I have edited the code to show how it is preceded by an async call to an http service
Yes, you are using it right. As #Bergi said, you can just return the run request.
It is safe to nest deferred as long as your promises are resolved synchronously or using db promises (always asynchronous).
Nesting deferred is generally fine, looping is to be worry about, such as orders.forEach for unexpected large loop. Browsers are not happy with long transaction (yes, can cause mysterious error or crash).
Like the way using count to check for record existance. I think it should be ydn.db.KeyRange.only(workOrder.id), since count require a key range input argument.
I have a javascript app saving all data on server, then use REST API communicate server and client.
They works fine, until we start have more and more nested async call or nested sync call which hiding async call. For example:
function asyncFoo(callback) {
callback();
}
function syncCallHidingAsyncCall(){
syncStuff();
asyncFoo(function(){
syncFoo()
});
}
function nestedAsyncCall(callback){
asyncFoo(function(){
anotherAsyncCall(callback);
})
}
// this make refactor code become so hard.
// if we want add step2() after nestedAsyncCall();
// instead of add one line of code
// we need first add callback param in every asyncCall, then pass step2 as a callback
And some unnecessary async call:
// we actually only verify this once.
function isLogin(callback){
if (!App._user) {
ServerApi.getCurUser(function(data){
App._user = data.user;
callback(App._user)
});
}
callback(App._user)
}
function syncCallNeedVerfifyLogin(callback){
// user only need login once, so in most case this is really only a sync call.
// but now I have to involve a unnecessary callback to get the return value
isLogin(function(){
callback(syncStuff())
})
}
So after the project become bigger and bigger, we start forgot their relationship, which one need wait, which one will do magic. And more and more function become async only because some very small thing need be verify on server.
So I start feel their must be some design problem in this project. I am looking for the best practice or design patter, or some rules need follow in this kind heavy communicate app.
Thanks for help.
They exist in several patterns to manage asynchronous data exchange and routine execution. They are called in different names as well:
Promises
EventEmitters
Deferred Objects/Deferreds
Control Flow Libraries
Futures
Callback aggregators
Observer / Publisher-Subscriber
A common implementation is jQuery's Deferred Objects which is also used in managing it's AJAX methods. In NodeJS, there is also AsyncJS and the native EventEmitter. There's even a 20-liner library made by some guy that implements EventEmitter which you could use.
As Bergi says in the comments, the pattern you're looking for is called deferred / promises. There's an implementation built into jQuery. From the docs:
a chainable utility object created by calling the jQuery.Deferred()
method. It can register multiple callbacks into callback queues,
invoke callback queues, and relay the success or failure state of any
synchronous or asynchronous function.
There are a variety of other implementations some of which are outlined in this stackoverflow question.
Make yourself a queue system, something like:
function Queue() {
this.queue = [];
}
Queue.prototype.i = -1;
Queue.prototype.add = function(fn) {
if (typeof fn !== "function")
throw new TypeError("Invalid argument");
this.queue.push(fn);
}
Queue.prototype.next = function() {
this.i++;
if (this.i < this.queue.length) {
this.queue[this.i].appy(this, arguments);
}
}
Queue.prototype.start = function() {
if (this.i !== -1)
throw new Error("Already running")
this.next.apply(this, arguments);
}
And use it like this:
var q = new Queue();
q.add(function() {
// do something async
// In the callback, call `this.next()`, passing
// any relevant arguments
})
q.add(function() {
// do something async
// In the callback, call `this.next()`, passing
// any relevant arguments
})
q.add(function() {
// do something async
// In the callback, call `this.next()`, passing
// any relevant arguments
})
q.start();
DEMO: http://jsfiddle.net/4n3kH/
I have a problem at work: I have a section of installations which are dependent on servers. I want to do this: When a user deletes a server, it loops through installations collection and deletes all dependent installations. For that I use jQuery 'when' function, which is said to wait for a response from the server and then move on to 'then' function. It works flawlessly when there is only one dependent installation. A problem occurs when there are more installations, however, because it moves to the 'then' function immediately after receiving a JSON response.
The question is: How do I make 'when' function wait for all server responses? Eg. I send out three delete requests through $.postJSON and want to move on after I get all three responses. If it's not possible with 'when', what should I use to make it happen? If it helps, I maintain all my entities collections with KnockoutJS. Thanks!
EDIT:
I have it like this:
$.when(DeleteDependentInstallations())
.then (function() {
...
});
DeleteDependentInstallations looks like (pseudocode):
Search the installations collection;
If installation.ID equals server.InstallationID
{
Add to dependent installations collection;
}
Repeat until the whole collection is searched;
for (i = 0; i < dependentInstallations.length; i++)
{
DeleteInstallation(dependentInstallations[i]);
}
DeleteInstallations is a simple function using $.postJSON function.
The problem is the .then function executes immediately after the first JSON response.
I think you need to have DeleteDependentInstallations return an array of JQuery deferreds. $.when allows you to pass multiple arguments to it in order to let it know it has to wait for each one.
I don't know the whole context of what you're doing, but I think something like this might work:
function DeleteDependentInstallations() {
var installations = getDependantInstallations();
var promises = [];
for (var i = 0; i < installations.length; i++) {
var installation = installations[i];
promises.push(DeleteInstallation(installation));
}
return promises;
}
function DeleteInstallation(installation) {
//do whatever here, but return the result of $.ajaxPost
return $.post('/foo', installation);
}
Now when you use that method, it should wait for all returned promises to complete.
$.when.apply(null, DeleteDependentInstallations()).then(function() { alert('wee!'); });
The .apply() is so we can pass an array as an arguments collection.
EDIT: I was confusing "deferreds" and promises in my head. Deferreds are what the $.ajax calls return, and a promise is what the $.when() function returns.
EDIT2: You might also want to look at the .done() method, if the behavior of .then() doesn't suit your needs.