Difficulty wrapping a javascript behavior and holding it for later - javascript

I am getting into programming with javascript and using Promises, right now using Q.js. I have finally gotten to a point where I understand what I am doing, but am having a difficult time with a specific behavior.
I have one situation where I have reasonably similar code repeated several times. It basically goes like this ...
{
// start
var deferred = Q.defer();
// do something {
deferred.resolve();
}
return deferred.promise;
}
Okay, that's all fine and good, but repeating all of this every time was getting annoying, so I attempted to wrap it up in something. This is just an example, it is not the entire javascript file, since most of the other parts are not relevant.
{
var list = [];
queue = function(f) {
var deferred = Q.defer();
list.push(f(deferred));
return deferred.promise;
}
{
queue(function(deferred){
// do some work
// we want the deferred here so we can resolve it at the correct time
deferred.resolve();
});
}
}
The problem is that I don't want this to run the instant I queue it up. I basically want to build the list, and then run it later. I am running the list using the reduce function in Q.js
{
return list.reduce(function(i, f) {
return i.then(f);
}, Q());
}
But this is kind of counter to my goal, since I really don't intend to run them at the same time they are queued. Is there a way to save the execution for later and still pass the deferred object through the function?
Update
I was asked what I expect the code to do, which is a fair question. I'll try to explain. The purpose of this is to split up the logic because I am using ASP.NET MVC, and so I have _Layout pages, and then normal views - so there is logic that cannot run until other things are completed, but some times that is on a per-page basis. This method was contrived to deal with that.
Essentially it works like this ...
Loader.js
This is, for lack of a better term or current implementation, a global object. I have plans to change that eventually, but one step at a time.
{
var Loader = {};
var list = [];
initialize = function() {
Q().then(step1).then(step2).then(process).then(finalStep);
};
queue = function(f) {
// push the given function to the list
};
process = function() {
return list.reduce(function(i,f){
return i.then(f);
}, Q());
};
step1 = function() { // generic example
// create a promise
return deferred.promise;
}; // other steps are similar to this.
return Loader;
}
_Layout
<head>
#RenderSection("scripts", false)
<script type="text/javascript">
// we have the loader object already
Loader.initialize();
</script>
</head>
Index
#section Scripts {
<script type="text/javascript">
Loader.promise(function(deferred){
// do something here.
deferred.resolve();
}));
</script>
}

You could use a closure.
queue(function(deferred) {
return function() {
// this is the actual function that will be run,
// but it will have access to the deferred variable
deferred.resolve();
};
});

I think you should do something like
var Loader = {
promise: function(construct) {
var deferred = Q.defer();
construct(deferred);
return deferred.promise;
},
queue: function(f) {
this.ready = this.ready.then(f);
},
ready: Q.Promise(function(resolve) {
window.onload = resolve; // or whatever you need to do here
// or assign the resolve function to Loader.initialize and call it later
})
};
Then Loader.queue() functions that return other promises.

Related

How to resolve race condition when using $.when

I am trying to use jQuerys $.when() to load a bunch of localization resources before initializing the control on the client side:
var fooControl = (function($, kendo, _) {
var
initResources = function() {
return $.when(
window.clientResources.getAll("Messages").done(function(d) {
resources["Messages"] = d;
}),
window.clientResources.getAll("Cost").done(function(d) {
resources["Cost"] = d;
})
);
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init(options));
});
What I want to achieve is, that initResources waits until the resources are loaded up and assigned to their variables. They are either loaded up from an API endpoint or localStorage, if the data is cached.
What actually happens is, that I am receiving an error Cannot read property 'CostType' of undefined, which indicates, that the Cost resources haven't been fully loaded yet.
So I suspect, that the calls to window.clientResources.getAll() are being resolved properly, but not the following .done method and this then results in a race condition, the resources are losing.
How can I make sure, that the whole call stack, including the assignment of the resources variable has been resolved and only then the following init function is called?
You are invoking the init immediately and passing its return value as success callback handler, A simple solution would be to use a anonymous method
fooControl.initResources().then(function(){
fooControl.init(options);
});
You could use $.Deferred() for this, then resolve that only when the resources have been loaded.
Also as Satpal noted, then needs to be passed either an anonymous function or a function reference.
var fooControl = (function($, kendo, _) {
var initResources = function() {
var deferred = $.Deferred();
$.when(
window.clientResources.getAll("Messages"),
window.clientResources.getAll("Cost")
).done(function(msgData, costData) {
resources["Messages"] = msgData;
resources["Cost"] = costData;
deferred.resolve();
});
return deferred.promise();
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init.bind(this, options));
});

chaining jquery .when().then() in a loop with fixed end of chain call

The closest answer I could find was this https://stackoverflow.com/a/17216555/2834734
The most common use for .then is chaining ajax requests:
$.ajax({...}).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); });
this can easily be done in a loop
However it's the looping procedure I'm having difficulty with plus I have some unusual circumstances.
A brief explanation is, I have an array of requests that I need to loop through, some will invoke an ajax load and others will not. I need them to run consecutively but also run a specific function call at then end.
Here is a simple(I hope) sample of my situation:
// Here is my flow of logic
var thingsToDo = new tasks(); // Initiate the constructor, explained below
// Loop through the requests array and process them consecutively
for (var i in thingsToDo.requests) {
$.when(thingsToDo.jqxhr).then(function() {
thingsToDo.requests[i].fn();
})
}
// Run my final function at the end of the chain.
$.when(thingsToDo.jqxhr).then(function() {
runOnceAllComplete();
});
This is the constructor class the above is based on.
// Constructor
function tasks() {
_tasks_ = this; // automatic global var
this.jqxhr = undefined; // Var to monitor ajax calls
this.requests = [ // list of tasks to run.
{
url: 'file1.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
}, {
url: 'file2.php',
fn: function() {
_tasks_.load(this.url);
console.log('file2 loaded');
}
}, {
noUrl: true, // Note there is no file to load here
fn: function() {
console.log('no file here to load, but process something else');
$('body').css("background-color", "blue");
}
}, {
url: 'file3.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
},
];
this.load = function(file) { // This runs the ajax call and resets this.jqxhr
this.jqxhr = $.get(file);
}
}
function runOnceAllComplete() {
alert('hooray!, we finished');
}
The tricky part I have is the requests are created dynamically so there can be 1-n many requests to perform, which is why I chose to loop, and they must be performed in that order.
As mentioned some requests will invoke an ajax call and others may not, this doesn't seem to break $.when().then(), but the problem is the loop continues before the promise is resolved and my final function happens before the final request.
Still trying to get my head around promises, the first time I've used them.
Try including return statement at fn , this.load ; adding .promise() chained to $("body") at fn to return a jQuery promise object ; using Function.prototype.apply() , $.map() at $.when()
fn: function() {
// added `return`
return _tasks_.load(this.url);
}
this.load = function(file) {
this.jqxhr = $.get(file);
// added `return`
return this.jqxhr
}
fn: function() {
console.log('no file here to load, but process something else');
// added `return` , `.promise()`
return $('body').css("background-color", "blue").promise();
}
$.when.apply($, $.map(thingsToDo.requests, function(task) {
return task.fn()
})).then(runOnceAllComplete)
See also Pass in an array of Deferreds to $.when() , What does $.when.apply($, someArray) do?
however I'm encountering a problem, using the .map() it doesn't wait
for each request to complete before processing the next one. I need
each one to complete before moving to the next.
Try using .queue() , which will calls functions in queue sequentially, and only when next is called at current function
$(thingsToDo).queue("tasks", $.map(thingsToDo.requests, function(task) {
return function(next) {
// call next function in `"tasks"` queue
// when current function completes using `.then(next)`
return task.fn().then(next)
}
})).dequeue("tasks").promise("tasks").then(runOnceAllComplete)
See .queue() , .promise() , Execute function queue in javascript

Avoiding duplicate asynchronous service initialisations in Angular.js

I have an Angular.js service which delivers its results asynchronously, after looking around for a while the main pattern for doing this seems to be using $q promises like this
angular.module('fooApp').factory('foo', function ($q) {
var result;
function build() {
var d = $q.defer();
longAsyncInit(function(data) {
result = data;
d.resolve(result);
});
return d.promise;
};
return {
get: function () {
if (result) {
return $q.when(result);
} else {
return build();
}
}
}
});
The problem is that I have a number of services which have this service as a dependency and get is called multiple times before the first longAsyncInit ends (which means that longAsyncInit gets called multiple times, each time creating a new promise). In my case this is unacceptable, I really need longAsyncInit to be called once, no more. I'm currently addressing this issue like this
angular.module('fooApp').factory('foo', function ($q) {
var result
var d;
function build() {
d = $q.defer();
longAsyncInit(function(data) {
result = data;
d.resolve(result);
});
return d.promise;
};
return {
get: function () {
if (result) {
return $q.when(result);
} else if (d) {
return d.promise;
} else {
return build();
}
}
}
});
This means if longAsyncInit is already ongoing when a get() call is made, it returns the current promise, instead of creating a new one and calling longAsyncInit again. This seems to work but feels inelegant and fragile, is there a better way of doing this?
You are looking for debounce method to solve problem.
From Underscore library documentation what _.debounce() do
Creates and returns a new debounced version of the passed function
which will postpone its execution until after wait milliseconds have
elapsed since the last time it was invoked. Useful for implementing
behavior that should only happen after the input has stopped arriving.
For example: rendering a preview of a Markdown comment, recalculating
a layout after the window has stopped being resized, and so on.
Some more explanations
Can someone explain the "debounce" function in Javascript
Some content to read:
http://davidwalsh.name/javascript-debounce-function

flickrapi (js) multiple async calls in a loop

I allmost banged my head into the wall because I can't get the following code too work. I'm trying to code a photo gallery with the flickrApi and have problems with multiple async calls. But perhaps there is a cleaner solution to code this.
openPhotoset() is called when clicking the link of a photoset. Unfortunately getting the description of a photo I need to use a different method, which means another async call. I'm looping through the data, but because I make the call in a loop (that's when I have the photo-id available) the deferred of openPhotoset() doesn't resolve after looping but before. I read and have seen examples of $.when() used in a loop, filling an array with deferreds and checking with $.when but I seem to fail horribly at it. Is this the solution I need or is there another road to salvation? ;)
I want to execute different functions after all calls within openPhotoset() has completed.
function openPhotoset(photosetId) {
var currentPhotoset = [],
deferred = $.Deferred();
_requestPhotosOfSet(photosetId).done(function(data){
$(data.photoset.photo).each(function(i, photo){
var objPhoto = {};
objPhoto.id = photo.id;
objPhoto.title = photo.title;
objPhoto.farm = photo.farm;
objPhoto.server = photo.server;
objPhoto.secret = photo.secret;
// get photo description
requestPhotoInfo(photo.id).done(function(data) {
objPhoto.description = data.photo.description._content;
currentPhotoset.push(objPhoto);
}).then(function() {
// TODO: renders with each iteration, shouldnt!
var template = $('#li-gallery').html(),
result = Mustache.render(template, {currentPhotoset:currentPhotoset});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
deferred.resolve();
});
});
});
return deferred;
}
You can do this by changing .done() for .then() in a couple of places, and rearranging things a bit - well quite
a lot.
I think you've probably been searching for something like this :
function openPhotoset(photosetId) {
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(function(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
return {
id: photo.id,
title: photo.title,
farm: photo.farm,
server: photo.server,
secret: photo.secret,
description: data.photo.description._content
};
});
}).get();//.get() is necessary to convert a jQuery object to a regular js array.
return $.when.apply(null, promises).then(function() {
var template = $('#li-gallery').html(),
result = Mustache.render(template, {
currentPhotoset: Array.prototype.slice.apply(arguments)
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
});
});
}
The main difference here is the creation of an array of promises as opposed to an array of photo objects, and allowing the promises to convey the data. This allows $.when() to fire off a callback when all the promises are fulfilled - ie when data objects have been composed for all photos in the set.
Note the use of .map() instead of .each(), thus simplifying the creation of promises.
And finally, the overall promise returned by openPhotoset() allows whatever action to be taken on completion of the whole process. Just chain .then().
openPhotoset(...).then(function() {
// here, do whatever
});
EDIT
The overall pattern is probably easier to understand if the inner workings are pulled out and rephrased as named promise-returning functions - getPhotoInfoObject() and renderData().
function openPhotoset(photosetId) {
function getPhotoInfoObject(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
//$.extend() is much less verbose than copying `photo`'s properties into a new object longhand.
return $.extend(photo, {description: data.photo.description._content});
});
}
function renderData() {
var template = $('#li-gallery').html(),
currentPhotoset = Array.prototype.slice.apply(arguments),
result = Mustache.render(template, {
currentPhotoset: currentPhotoset
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
}
// With the inner workings pulled out as getPhotoInfoObject() and renderData(),
// the residual pattern is very concise and easier to understand.
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(getPhotoInfoObject).get();
return $.when.apply(null, promises).then(renderData);
});
}
I was so blinded by the defereds and $.when function that I didn't notice all I needed was to create a counter and count down each time requestPhotoInfo was done and after render the html

Accurately tracking Angular's active/done promises invoked by $q.defer

For my angular application, i'm trying to track how many promises have been processed and how many still need processing.
My code decorates $q with wrapper methods that update a simple counter whenever an operation is started and whenever one is finished, seems simple:
angular.module('DuckieTV',[])
.config(function($provide) {
var count = window.promiseStats = {
open: 0,
done: 0
};
$provide.decorator('$q', function($delegate) {
function decorate(p) {
p._then = p.then;
p.then = function(thenFn, errFn, notifyFn) {
count.open++;
return p._then(function() {
count.done++;
if(thenFn) return thenFn.apply(this,arguments)
}, function() {
count.done++;
if(errFn) return errFn.apply(this,arguments)
}, notifyFn);
};
p._finally = p.finally;
p.finally = function(callback) {
count.done++;
p._finally(callback)
}
p._catch = p.catch;
p.catch = function(callback) {
count.done++;
p._catch(callback)
}
return p;
}
var d = $delegate.defer;
$delegate.defer = function() {
var deferred = d();
decorate(deferred.promise);
return deferred;
};
return $delegate;
});
})
The fun begins when I'm noticing a discrepancy between started/finished promises. After just a few minutes of performing lots of deferred operations it can become a > 15% percentage overall.
Example console output:
promiseStats
Object {open: 99, done: 95}
Math.floor(promiseStats.done / promiseStats.open * 100);
94
after some work (import operations)
promiseStats;
Object {open: 185, done: 172}
Math.floor(promiseStats.done / promiseStats.open * 100);
92
My actual question: Can anyone tell me if i'm missing something with this implementation?
As far as I know, I haven't missed optional implementations and all the promises that i'm firing via .then are properly coded
Can anyone tell me if i'm missing something with this implementation?
I can see two issues with your implementation:
Your decorated catch and invoke methods only ever increase the done count, never the open one. Since you not seeing this, I'd guess that you haven't used either in your code.
By introducing an onfail handler to every .then() call, you implicitly catch all errors and severely hurt control flow. You might be able to fix this by appending the following line to your handler code:
…
else throw arguments[0];
I'm noticing a discrepancy between started/finished promises
I don't think this is caused by your implementation of the counters. Rather you really have some ever-pending promises in your code; i.e. deferreds that are never resolved. This for example might be caused by mistakes in code that uses the deferred antipattern.
My code decorates $q with wrapper methods that update a simple counter whenever an operation is started and whenever one is finished, seems simple:
In fact, it updates a counter whenever a listener is attached, i.e. a then/catch/finally method is called. I'd propose a simpler implementation, that counts on creation and doesn't even need to overwrite the methods:
$provide.decorator('$q', function($delegate) {
var defer = $delegate.defer;
$delegate.defer = function() {
var deferred = defer();
count.open++;
deferred.promise.finally(function() {
count.done++;
});
return deferred;
};
return $delegate;
});
The plot thickens.
I've been profiling and tracing code based upon the excellent code by #Bergi and noticed that consistently promise #31 was left open, so I added a debug statement right there:
.config(function($provide) {
var count = window.promiseStats = {
open: 0,
done: 0,
promises: {}
};
$provide.decorator('$q', function($delegate) {
var defer = $delegate.defer;
$delegate.defer = function() {
count.open++;
var traceId = count.open;
if(traceId == 31) {
debugger;
}
var deferred = count.promises[traceId] = defer();
console.timeline('promise ' +traceId);
console.profile('promise '+traceId);
deferred.promise.finally(function() {
count.done++;
console.timelineEnd('promise ' +traceId);
console.profileEnd('promise '+traceId);
delete count.promises[traceId];
});
return deferred;
};
return $delegate;
});
})
This dropped me directly inside an angular-core template request that seems to have a different kind of promise handling.
I'm still trying to assess wether or not this is a problem. as it does seem to unregister with some specialized code.

Categories

Resources