Dynamically adding sequential actions using deferreds - javascript

Here is my current code: https://gist.github.com/benjamw/f6d5d682caddd4c1e506
What I'm trying to do is: based on what the URL is when the user hits the page, pull down different data from the server, and when that's all done, render the page with Hogan.
My problem is that step B needs data from step C to render properly, and step C needs data from step B to pull the right data, but step C can be pulled by itself if that's the page the user requests (the data needed by C to pull properly is part of the URL when going directly to C).
I have a Deferred being stored in Thing that gets resolved in the various pull steps and triggers the render, but in pull_B, I don't want it resolved until it gets and cleans data from both pull_B and pull_C. But if the user goes direct through C, then I want it to resolve just fine.
How can I dynamically add a promise to the when in the init() function when the process goes through the B path to include the C path?
Or, how can I make B pass it's promise into C and then resolve it there, but still keep the functionality of being able to go through C itself, and have it still resolve the main deferred object without going through B first?
I'm trying really hard not to drop into callback hell for this, but I'm finding it difficult to do so.

The crux of the problem is clearly the relationship between B and C, which, in summary, appears to be :
If B, pull-C().then(pull-B);
If C, pull-B().then(pull-C);
In the current attempt, you are running into problems by trying to code the flow logic inside pull-B() and pull-C(), which is ultimately possible but complex.
A simpler strategy is to make the pull_X() functions very simple promise-returning data retrievers, and to code the flow logic and data cleanups inside the switch/case structure in .init(). You will see what I mean in the code below.
Apart from being simpler, this will also avoid any chance of circular dependencies between pull_B() and pull_C().
By fully exploiting promises, you will also find that :
the need for this.dfd disappears (in favour of returning promises from functions).
the need for this.data disappears (in favour of allowing promises to deliver the data).
the need for a callback to be passed to .pull() disappears (in favour of chaining .then() in the caller). Thus, callback hell disappears.
Try this :
(function($) {
"use strict";
/**
* The Thing
*
* #constructor
*/
function Thing( ) {
/* properties */
this.url = [
/* path */,
/* id */
];
}
Thing.prototype.pull = function(url, args, type) {
return $.ajax({
type: type || 'GET',
url: foo.root + url,
data: $.extend({}, args || {}),
dataType: 'json'
});
};
Thing.prototype.pull_As = function() {
return this.pull('a', this.query);
};
Thing.prototype.pull_A = function() {
this.nav = false;
return this.pull('a/'+ this.url[2]);
};
Thing.prototype.pull_B = function() {
return this.pull('b/' + this.url[2]);
};
Thing.prototype.pull_C = function(id) {
return this.pull('c/' + id || this.url[2]);
};
Thing.prototype.pull_D = function() {
return this.pull_As();
};
Thing.prototype.render = function(data) {
var i, len, html,
that = foo.thing, /* because 'this' is the promise object */
title = document.title.split('|');
for (i = 0, len = title.length; i < len; i += 1) {
title[i] = $.trim(title[i]);
}
title[0] = $.trim(that.title);
document.title = title.join(' | ');
html = Hogan.wrapper.render({
'data': data,
});
$('#thing_wrapper').empty().append(html);
};
Thing.prototype.init = function( ) {
var promise,
that = this;
switch (this.url[1].toLowerCase( )) {
case 'a':
promise = this.pull_A().then(function(data_A) {
/* ... do A data cleanup */
return data_A;//will be passed through to .render()
});
break;
case 'b':
promise = this.pull_C().then(function(data_C) {
//Here an inner promise chain is formed, allowing data_C, as well as data_B, to be accessed by the innermost function.
return that.pull_B().then(function(data_B) {
var data = ...;//some merge of data_B and data_C
return data;//will be passed through to .render()
});
});
break;
case 'c':
var id = ???;
promise = this.pull_C(id).then(function(data_C) {
/* ... do C data cleanup */
return data_C;//will be passed through to .render()
});
break;
case '':
default:
promise = this.pull_D().then(function(data_D) {
/* ... do D data cleanup */
return data_D;//will be passed through to .render()
});
}
promise.then(this.render, console.error.bind(console));
};
window.Thing = Thing;
})(jQuery);
Note in particular that a promise or data is returned from the various functions.
I doubt my attempt is 100% correct. The overall structure should be fine, though you will need to take a close look at the detail. I may have misunderstood the B/C dependencies. With luck, it will be simpler than what I have coded.
Edit: code amended in light of comments below.

Related

How do I use the value from m.request in MithrilJS when using background:true?

I'm using m.request in a project, and since I have a request that can be potentially long running, I want to run it with background:true. However, it seems like the value never gets set to the generated m.prop.
I've made a jsFiddle with an example based on this Stack Overflow answer: http://jsfiddle.net/u5wuyokz/9/
What I expect to happen is that the second call to the view should have the response value in ctrl.test.data(), but it seems to still have undefined. At Point A in the code, it logs the correct value. However, at Point B, it logs false, undefined and then true, undefined.
I'm not sure if I'm doing something incorrectly, or if this the expected behavior.
Code from the jsFiddle:
var requestWithFeedback = function(args) {
var completed = m.prop(false)
var complete = function(value) {
completed(true)
return value
}
args.background = true
return {
data: m.request(args).then(complete, complete).then(function(value) {
//Point A
console.log(value);
m.redraw()
return value
}),
ready: completed
}
};
var mod = {
controller : function() {
this.test = requestWithFeedback({
method : "POST",
url : "/echo/json/",
serialize: serialize,
config: asFormUrlEncoded,
data : {
json : "{\"name\" : \"testing\"}"
}
});
},
view : function(ctrl) {
//Point B
console.log(ctrl.test.ready(), ctrl.test.data());
return m("div", ctrl.test.ready() ? 'loaded' : 'loading');
}
};
Edit: The problem is that m.redraw is called before the data is assigned. Instead you could create a m.prop for data and let the ajax request assign that value when completed. requestWithFeedback will then look like this:
var requestWithFeedback = function(args) {
var data = m.prop()
args.background = true
m.request(args).then(data).then(function() { m.redraw() })
return {
data: data,
ready: function() {return !!data()}
}
};
Here's a modified version of your fiddle using this code: http://jsfiddle.net/u5wuyokz/11/
When using background: true, Mithril's components branch or any other system that executes controllers in the same 'tick' as views, m.requests made in the controller will not have resolved by the time they are invoked by their respective views.
It is therefore recommended to always use the initialValue property of m.request if you're using background: true. The canonical example is to have an initial value of [] when you make a request for a list of entries:
var projectsModule = {
controller(){
this.projects = m.request( {
background : true,
initialValue : [],
url : '/projects.json'
} );
},
view( ctrl ){
return m( 'ul', ctrl.projects.map(
project => m( 'li', project.name )
) )
}
}
This solves the practical problems of views breaking when they expect to be able to work with m.request return values in a generic way, but doesn't address the more complex case in your example, where a ready flag is desirable to indicate a 'loading' state. I have a generic API that consumes a model getter function and an optional initial value. It has next, then, hasResolved, isPending, get and set methods: this allows views to be more flexible in the way they query the state of asynchronous models. hasResolved indicates whether the method has ever resolved, and next returns a promise that will resolve either with the current request, or when the next request is resolved if isPending is false.

Traversing promises in Q

In Scala I can take a list of values, map a future-returning function across them, and get back a future that will collect the values of those futures into a list (or fail with the first error). More concretely:
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def doSomething(i: Int): Future[Int] = future(i + 1) // Not really doing much.
val incremented = Future.traverse(List(1, 2, 3))(doSomething)
In this case the result will just be the incremented list:
scala> incremented.onSuccess { case xs => println(xs) }
List(2, 3, 4)
I could alternatively create a list of futures and then turn them into a future containing the same result:
val incremented = Future.sequence(List(1, 2, 3).map(doSomething))
This will give me the same thing, but it creates an extra intermediate collection and is a little more noisy.
I want to do something like this with promises in Q, and it looks like Q.all is more or less sequence:
function doSomething(i) { return Q.fcall(function () { return i + 1; }); }
Q.all([1, 2, 3].map(doSomething)).then(function (xs) { console.log(xs) })
Is there a way for me to write a more traverse-like version? It's such a basic operation that it seems like there must be a way to do it, but this is my first afternoon with Q and I'm still working through all the overloads of fcall and friends.
Not directly an answer to your question, but AngularJS uses an extremely reduced version of Q ($q), so there you definitely have to implement this behavior yourself.
Here's one approach:
var traverse = function(fn /*values*/) {
var values = _.rest(arguments);
var promises = _.map(values, fn);
return $q.all(promises);
};
Full example: http://plnkr.co/edit/PGp7QbQYMjOknJwSEn8E
Or with separate parameter lists as in Scala:
var traverse = function(/*values*/) {
var values = arguments;
return function(fn) {
var promises = _.map(values, fn);
return $q.all(promises);
}
};
Full example: http://plnkr.co/edit/pWoGGaZobbx61tAmUWr9?p=preview
You can use Promise Chaining + Static values (instead of promises) for methods and do something like:
Q.all([1,2,3])
.then(function(xs) {
return _(xs).map(doSomething)
})
.then(function(xs) {
console.log(xs);
});
If you want a traverse function like that one, you can easily implement it yourself
Hope it helps!

can I emulate a C-like array of pointers in javascript?

I'd like to be able to store the addresses of a bunch of different variables in an array. This allows me to access the variables by name or iterate through them if I need to. Is this possible in JS?
(function(ns){
ns.obj = new function(){
var foo = "foo";
var bar = "bar";
//i really want this:
//var ary = [&foo, &bar];
var ary = [foo, bar];
this.print = function() {
console.log( foo );
console.log( bar );
}
this.setFoo = function( newFoo ) {
//i really want this:
//*(ary[0]) = newFoo;
ary[0] = newFoo;
}
this.printAry = function() {
for( var i=0; i < ary.length; ++i ) {
console.log( ary[i] );
}
}
};
}(window.ns = window.ns || {}) );
ns.obj.print();
ns.obj.setFoo("newfoo!");
ns.obj.printAry();
ns.obj.print();
I looked at this:
JavaScript array of pointers like in C++
But I'd like to be able to use an element of ary on the LHS of an assignment and I don't think that example works in this situation.
WHY ON EARTH DO I WANT TO DO THIS?
A lot of comments so far have (rightfully) asked why I'd want to do this. I'm dealing with a proprietary API that involves an asynchronous object initialization mechanism. Basically I create an instance of an object and then pass it to this initializer to be able to actually use it. The initializer includes a field for an onSuccess handler to notify of successful initialization. My fully initialized object is passed as an argument into this success handler so that I can grab a reference to it.
I'm then free to initialize my next object. It looks kinda like this:
var a = new api.ApiObject();
var b = new api.ApiObject();
var c = new api.ApiObject();
var d = new api.ApiObject();
//omg this is ugly
api.initializeObject( {
objToInit: a,
onSuccess: function(args) {
a = args.obj;
api.initializeObject( {
objToInit: b,
onSuccess: function(args) {
b = args.obj;
api.initializeObject( {
objToInit: c,
onSuccess: function(args) {
c = args.obj;
api.initializeObject( {
objToInit: d,
onSuccess: function(args) {
d = args.obj;
}
} );
}
} );
}
} );
}
} );
a.doCoolStuff();
//and so on
This deeply nested mess just gets worse as I add more api.ApiObjects(). So what do I do to fix this? I can't change the API, but maybe a recursive function could help:
//maybe a recursive function could make this more concise?
function doInitialize( ary ) {
api.initializeObject( {
objToInit: ary[0];
onSuccess: function(args) {
//i'd like to assign this passed in reference to my local
//reference outside this function (var a, b, etc).
//An array of pointers would be useful here.
//how else can I get this assigned out, cuz this doesn't work...
ary[0] = args.obj;
if( ary.length > 1 ) {
ary.splice( 0, 1 );
doInitialize( ary );
}
}
}
}
doInitialize( [a,b,c,d] );
//this won't work because I don't have a reference to the fully initialized object
a.doCoolStuff();
So maybe the better question is: is there an established pattern to deal with asynchronous success chaining like this? I think I've seen other public JS frameworks (like dojo) use this sort of onSuccess chaining... how do I make this not ugly?
I might suggest that if your primary purpose for this is convenience as regards nesting of asynchronous callbacks, that you should consider a deferred/promise system.
I've written a couple of different promise libraries by hand.
jQuery comes with one built in (as do most "ajax libraries").
Here's what this might look like, in a better world:
doThingOne()
.then(doThingTwo)
.then(doThingThree)
.then(launch);
Assuming that doThingOne returns a promise.
A more familiar looking interface for people who use jQuery (or most other promise-using large libraries), might look like this:
var imageLoader = $.Deferred(),
loading = imageLoader.promise();
loading
.done(gallery.render.bind(gallery))
.done(gallery.show.bind(gallery));
var img = new Image(),
url = "...";
img.onload = function () { imageLoader.resolve(img); };
img.onerror = function () { imageLoader.reject("error message"); };
img.src = url;
Very basically, the Deferred above will hold two private arrays (one for "success", one for "failure"), and will extend an interface which allows the async part of the application to "succeed" or "fail", and will pass in whatever is chosen to be data/a callback/etc.
It also extends a promise method, which returns a promise object, containing subscription functions for the two private arrays. So you pass the promise object around to interested parties, and they subscribe callbacks to be iterated through, on success/failure of the async operation (and passed anything which is passed to the .resolve/.reject method of the operation).
This might seem like an inversion or extension of just adding a custom-event/listener/etc...
And it is.
The benefit of the abstraction is that the interface is cleaner.
Hiding this stuff inside of object interfaces, and just passing async promise-objects around can make your code look 100% synchronous:
var images = ImageLoader(),
gallery = ImageGallery(),
photo;
photo = images.load("//url.com/image.png"); // assuming `.load` returns a promise object
gallery.show(photo); // just a promise object, but internally,
//`.show` would subscribe a private method to the promise object
And doing things like having three separate async operations, which can arrive in any order, but must all be successful before advancing, then you can have something like this (again jQuery, but doing it by hand is possible, too).
$.when(promise_obj_1, promise_obj_2, promise_obj_3)
.done(nextPhase);
nextPhase, of course, being a callback which you anticipate to be fired if all three promises are successfully completed.
I'd be happy to provide implementation details for a barebones promise system, if you're like me, and don't like using different libraries without first understanding how each piece works on its own, and being able to replicate its functionality, without copying code.
The answer to the first part of your question is to use an object. You're thinking in C which doesn't have iteratable structs so C programmers reach for arrays. In JS objects are iteratable. So you should write it as:
ary = {
foo : 'foo',
bar : 'bar'
}
Or if we look at your second example:
var apis = {
a : new api.ApiObject(),
b : new api.ApiObject(),
c : new api.ApiObject(),
d : new api.ApiObject()
}
Now, as for the second part of your question. Your pseudo recursive code (pseudo because it's not really recursive in the stack sense since it's async) will now work with the apis object above. But you pass the keys instead of the object:
doInitialize( ['a','b','c','d'] );
Obviously, the bit above can be done dynamically by iterating through the apis object. Anyway, in the onSuccess part of the code you assign the result like this:
apis[ary[0]] = args.obj;
Oh, and obviously the objToInit should now be apis[ary[0]].
Now doing this should work as you expect:
apis.a.doCoolStuff();

Add methods to a collection returned from an angular resource query

I have a resource that returns an array from a query, like so:
.factory('Books', function($resource){
var Books = $resource('/authors/:authorId/books');
return Books;
})
Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).
For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.
The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.
So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):
var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
var service = $resource('/authors/:authorId/books'),
origQuery = service.prototype.$query;
service.prototype.$query = function (a1, a2, a3) {
var returnData = origQuery.call(this, a1, a2, a3);
returnData.myCustomMethod = function () {
// Create your custom method here...
return returnData;
}
}
return service;
});
Again, you will have to mess with it a bit, but that's the basic idea.
This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.
var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
var service = $resource('/authors/:authorId/books');
service.hasBookWithTitle = function(books, title){
//blah blah return true false etc.
}
return service;
});
then
books = Book.list(function(){
//check in the on complete method
var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).
If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:
var books = Book.query(function(data) {
data.hasBookWithTitle = function (title) { ... };
]);
Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg
app.factory("Books",
function ($resource) {
var self = this;
var resource = $resource("sample.json");
return {
get: function(id) { return resource.get(id); },
// implement whatever else you need, save, delete etc.
query: function() {
return resource.query(
function(data) { // success callback
data.hasBookWithTitle = function(title) {
for (var i = 0; i < data.length; i++) {
if (title === data[i].title) {
return true;
}
}
return false;
};
},
function(data, response) { /* optional error callback */}
);
}
};
}
);
Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.

Variable scope in Javascript Object

I'm discovering the concept of "objects" in JavaScript. I'm making an RSS Parser, and I have an error (commented).
function MyParser (feed_url) { // Construct
"use strict";
this.feedUrl = feed_url;
this.pubArray = [];
if (typeof (this.init_ok) == 'undefined') {
MyParser.prototype.parse = function () {
"use strict";
var thisObj = this;
$.get(this.feedUrl, function (data, textStatus, jqXHR) {
if (textStatus == 'success') {
var xml = jqXHR.responseXML,
//lastBuildDate = new Date($(xml).find('lastBuildDate').text());
items = $(xml).find('item');
items.each(function () {
var pubSingle = thisObj.makeObj($(this).find('pubDate').text(),
$(this).find('link').text(),
$(this).find('title').text(),
$(this).find('description').text(),
$(this).find('encoded').text(),
$(this).find('commentRss').text(),
$(this).find('comments').last().text());
thisObj.pubArray.push(pubSingle);
});
console.log(thisObj.pubArray); // OK
}
}, 'xml');
console.log(this.pubArray); // Empty
return (this.pubArray);
};
MyParser.prototype.makeObj = function (pubDate, pubLink, pubTitle, pubDesc, pubContent, pubComCount, pubComLink) {
"use strict";
var pubSingle = {};
pubSingle.pubDate = new Date(pubDate);
pubSingle.pubLink = pubLink;
pubSingle.pubTitle = pubTitle;
pubSingle.pubDesc = pubDesc;
pubSingle.pubContent = pubContent;
pubSingle.pubComCount = pubComCount;
pubSingle.pubComLink = pubComLink;
return (pubSingle);
};
}
this.init_ok = true;
}
If you look at the console.log(), you'll see that the line // OK is outputting my array correctly.
But later, when returning from $.get, my array is empty.
Does anybody have an idea why, and how to correct that please?
This is not a problem with variable-scope. The problem here is that you're working with asynchronous flow and you're not thinking correctly the flow.
Let me explain:
When you do your .get, you fire a parallel asynchronous process that will request information from the browser, but your main program's flow keeps going, so when you get to your "return" statement, your array has not been filled yet with the response from your get method.
You should use your array from inside the get callback and not outside of it, since you can't guarantee that the array will have the information you need.
Does it make any sense?
Let me know!
Further explanation
According to your comments, you're still doing something like this:
var results = MyParser(feed_url);
//code that uses results.pubArray
And you cannot do that. Even though you're setting your "pubArray" inside your .get callback, you're trying to use pubArray right after you called MyParser and that's before the .get callback is called.
What you have to do, is call your next step on your program's logic from within the .get callback... that's the only way you can be sure that the pubArray is filled with proper data.
I hope that makes it clearer.
This is because your line
console.log(this.pubArray); // Empty
is being called directly after you issue your Ajax request; it hasn't had time to fetch the data yet. The line
console.log(thisObj.pubArray); // OK
is being called inside the Ajax callback, by which time the data has been fetched.
Thank you all, and particulary #Deleteman .
Here is what I did:
$.get(this.feedUrl, 'xml').success(function () {
thisObj.handleAjax(arguments[0], arguments[1], arguments[2]);
$(document).trigger('MyParserDone');
}).error(function () {
$(document).trigger('MyParserFailed');
});
Then, when i enter "HandleAjax", i'm back in my object context, so "this" refers to my object and the right properties. The only "problem" is that I have to set a listener (MyParserDone) to make sure the parsing is finished.

Categories

Resources