0

In a qooxdoo class, I have a set of asynchronous methods that need to be chained (serialized):

main: function() {
    async1();
},

async1: function() {
    var json = new qx.data.store.Json("http://...");
    json.addListener("loaded", function(event) {
        ...
        async2();
    });
},

async2: function() {
    // similar to async1
}

As the number of the steps grows, the chain becomes hard to trace, and the code gets unreadable. What we can do is rewrite our code using Promises:

main: function() {
    new Promise(this.async1)
    .then(function() {
        return new Promise(this.async2);
    }).then(function() {
        return new Promise(this.async3);
    }).catch(...);
},

async1: function(resolve, reject) {
    var json = new qx.data.store.Json("http://...");
    json.addListener("loaded", function(event) {
        ...
        resolve();
    });
},

async2: function(resolve, reject) {
    // similar to async1
}

This works great but only until we add some real logic. Remember it's a qooxdoo class with a lot of stuff encapsulated into this. But suddenly it turns out that both async* methods and anonymous functions used in then() and catch() have their this context bound to window, a global object. To be able to use actual this, we can perform rebind:

main: function() {
    new Promise(this.async1.bind(this))
    .then(function() {
        this.doSomeStuff();
        return new Promise(this.async2.bind(this));
    }.bind(this)).then(function() {
        return new Promise(this.async3.bind(this));
    }.bind(this)).then(function() {
        this.doSomeFinalStuff();
    }.bind(this)).catch(function() {
        this.doStuffOnError();
    }.bind(this));
},

async1: function(resolve, reject) {
    this.doOtherStuff();
    var json = new qx.data.store.Json("http://...");
    json.addListener("loaded", function(event) {
        ...
        resolve();
    });
}

This finally works, but doh, ugly is the code! Are there any ways to get rid of those manual binds? Why isn't this implicitly bound when we reference this.async1, an instance method?

  • Just an observation: if you need to call a long chain of functions, regardless of whether they're asynchronous or not, I think there's a good chance there's something wrong with your overall design. (I may be wrong, of course - obviously I haven't seen the code.) As for functions not being bound by default: making this the default would prevent passing unbound functions around - essential for functional style of programming - there's no way to un-bind a function. And by the way: do you need the last `bind()` call above, if you use a `self` variable, as suggested in the accepted answer? – user625488 Mar 01 '15 at 06:54
  • I've seen the need for such a succession of asynchronous calls when you need several server-provided resources to perform an operation. What I'd do is to pack all the logic in a single function, which I'd pass as a callback for everything that needs to be fetched asynchronously. This function would first check if all preconditions are met, then proceed to perform all calculations. IMO, if possible, switching to such a code structure would significantly improve readability - instead of having probably related logic sprinkled across many functions, you have it all in one place. – user625488 Mar 01 '15 at 07:04
  • Alternatively, you can use an event-based structure - instead of passing callbacks, you have callbacks fire events on which you register an event handler. This would still improve the code structure, IMO. With plain promises, you miss the opportunity to use descriptive function names to document intention. With events, you get two possibilities for documentation: the event name and the handler name. – user625488 Mar 01 '15 at 07:06
  • Tried to come up with an idea of where chaining async calls might be best - if you implement a challenge-response authentication mechanism, I think it would be more descriptive to use a chain of async calls - the two solutions I described above don't provide strong, explicit guarantees of causal ordering (qooxdoo events provide implicit, though), but it's needed and documenting it is nice in case of challenge-response auth. But other than similar situations, I think chaining async calls isn't nice towards the readers of your code. – user625488 Mar 01 '15 at 07:14

2 Answers2

1

If you add var self = this; at the top, within all the functions you can refer to the object itself by calling self:

main: function() {
    var self = this;
    new Promise(self.async1)
    .then(function() {
        return new Promise(self.async2);
    }).then(function() {
        return new Promise(self.async3);
    }).catch(...);
},
  • Unfortunately, this will free us only of one half of binds (those for anonymous functions). async* methods will still have their `this` contexts to be rebound, otherwise it will default to `window`. – Martin Schmidt Feb 27 '15 at 03:30
1

Check this answer out

Basically, it doesn't look like you're doing anything wrong fundamentally. That's how the context is supposed to work. What you really need is some syntax sugar for your specific use case. You can make these sugar extensions yourself or use a library to do some heavy lifting.

You also could restructure your code like Danbopes suggests and organize it so you're doing less inline-binding. This would allow you to read your flow better.

Community
  • 1
  • 1
Dane O'Connor
  • 75,180
  • 37
  • 119
  • 173