0

I have an object that looks like this:

    var users = function(url){
        this.users = []

        console.time("api");
        d3.json(url, function(data){
            console.timeEnd("api");
            this.users = data.data
        })
    }

it's instatiated like this:

    var liveUsers = new users(apiPoint)

d3.json is an asynchonomous api-call. I wish to perform a callback once it's complete, preferably a chaned one, how can I do this?

Himmators
  • 14,278
  • 36
  • 132
  • 223
  • Give it a callback, or use any of the existing promise implementations (if d3.json doesn't already provide one) – Kevin B Dec 15 '14 at 15:21
  • 1
    Side note: `this` will have a different value inside `function(data)` than the `users` instance. [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) – Jonathan Lonowski Dec 15 '14 at 15:23
  • @JonathanLonowski so something like this=self whould be prefered? – Himmators Dec 15 '14 at 15:24

2 Answers2

3

All you need is to implement a callback system. Here's one simple way of doing it:

var users = function(url){
    var self = this;
    this.users = []

    console.time("api");
    d3.json(url, function(data){
        console.timeEnd("api");
        self.users = data.data
        self.cb && self.cb();
    })
    this.complete = function (fn) {
        this.cb = fn;
    };
}
var liveUsers = new users(apiEndpoint).complete(function (){
    console.log(this.users);
});

Still seems a bit overly-complicated to me, why do you need it to chain? why does users need to be a constructor? why does users even exist instead of simply using d3.json, which already has all of the functionality you are looking for in your question right out of the box?

Usually the point of abstracting a request behind a function is to avoid needing to specify the api endpoint so that if you need to change the endpoint you can do so all in one place. In this case you have to specify it with each request, making the name of the function kinda... pointless since it can be used to request from any endpoint.

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • if you modified `this.cb = fn` to `this.cb = fn.call(this)`, you wouldn't need to refer to `liveUsers` in the callback function, you could just say `this.users`. your overall point is valid though, for this simple case the architecture is substantial. upvote! – PlantTheIdea Dec 15 '14 at 15:51
  • pretty sure you don't even need to use `.call(this)`, since it's being executed as a property of `this`, it's `this` will be the instance. so you could just replace `liveUsers.users` with `this.users` without changing anything else. – Kevin B Dec 15 '14 at 16:35
  • ah not true ... the `this` is applied when the function is instantiated, not called, and since it is an anonymous function it would inherit either the `function` it is in or the `window` itself. the use of `.calL(this)` would need to be explicit. – PlantTheIdea Dec 15 '14 at 16:43
  • seems to work for me, http://jsfiddle.net/z5sk8e13/ could i be miss-interpreting what you're saying? – Kevin B Dec 15 '14 at 16:47
  • ah u know what, nvm duh ... complete is a method of the object itself, and that context is passed to the instantiation of any object in that function. sorry, brainfart. – PlantTheIdea Dec 15 '14 at 17:17
1

If you want to chain, just return this!

var users = function(url){
        this.users = []

        console.time("api");
        d3.json(url, function(data){
            console.timeEnd("api");
            this.users = data.data
        })

        return this;
    };

users.prototype.somethingElse = function(){
    console.log(this.users);

    return this;
};

var liveUsers = new users(apiPoint).somethingElse();

The use of return this keeps the chain going, and you can add additional functions to the class by adding prototype methods. The this is retained by using that prototype capability, however if you wanted to use another function that isn't associated with the class and still use that same this, then you'd need to get a little trickier:

var users = function(url){
        this.users = []

        console.time("api");
        d3.json(url, function(data){
            console.timeEnd("api");
            this.users = data.data
        })

        return this;
    },
    somethingElse = function(){
        console.log(this.users);

        return this;
    };

var liveUsers = new users(apiPoint);

// bunch of other codey stuffs

somethingElse.call(liveUsers);

By using .call() applying liveUsers as the first argument, it overrides whatever this the somethingElse function originally had, and gives it the context that you want (making this === liveUsers).

Hope this helps!

PlantTheIdea
  • 16,061
  • 5
  • 35
  • 40
  • Note however that if `d3.json` is asynchronous, this doesn't actually solve anything. you still have no way of knowing when it is safe to execute `somethingElse`. In your sample, you would be executing it before the array is populated. – Kevin B Dec 15 '14 at 15:30
  • Ah true ... somehow I totally missed the asychronous portion. If that is the case, then you're getting into things like promises. Totally doable (I've built a promise microlibrary called pledge myself), but much too long for this answer. – PlantTheIdea Dec 15 '14 at 15:31
  • @PlantTheIdea What would you advice? to use a library, work with some non-chaining callback or build it myself? – Himmators Dec 15 '14 at 15:33
  • for a simple case like this, @KevinB is right, a simple callback will do, although it will not be chainable. if you want to get into more advanced applications of it, i recommend delving into a promise library like q.js. if this is as intense as you get, no need for that though. – PlantTheIdea Dec 15 '14 at 15:33
  • 1
    or you could give my microlibrary a try ... https://github.com/planttheidea/pledge. It gives basic promise and defer capabilities, and minified / gzipped its less than 1KB. – PlantTheIdea Dec 15 '14 at 15:36
  • I tried going for a simple callback, but I would like to access a method from withing the object I just created, I tried using both this and liveUsers, neither worked. Updating question... – Himmators Dec 15 '14 at 15:43