-1

Since methods in Javascript can easily be rebound, some (many?) developers resorted to using JS's lexical scoping to achieve object encapsulation (i.e., private object state independent of the object the method is currently bound to). I found this particularly handy when working with promises like this:

function Something() {
    const that = this; // optional
    that.state = 0;
    this.doSomething = function() {
        console.log(that.state++);
    };
    return this;
}

const instance = new Something;
new Promise(resolve => resolve(instance)).then(instance.doSomething);
//output: 0

Unfortunately, this doesn't seem to be compatible with the current draft of Ecmascript 6 classes. This code:

class SomethingES6 {
    constructor(){
        this.state = 0;
    }
    doSomething(){
        console.log(this.state++);
    }
}

const instanceES6 = new SomethingES6;
new Promise(resolve => resolve(instanceES6)).then(instanceES6.doSomething);

Throws because the doSomething method isn't bound to instanceES6 when executed. Is there a way to achieve a behavior like the one exposed when exploiting the lexical scoping of "that" in the previous snippet?

I want a solution that enforces this behavior when defining the class - bluebird's (and other's) Promise.bind is not a solution! Personally I'd like a solution that works in node but, of course, a generic solution is preferable.

Holger
  • 1,648
  • 1
  • 16
  • 26
  • `bind` is absolutely a solution. Pre-binding is annoying. You also appear to be treating promises like a wrapped version of callbacks, which they aren’t; try `Promise.resolve(instanceES6).then(instance => instance.doSomething())`, e.g.. – Ry- Feb 10 '15 at 02:19
  • An important note: `instance.doSomething` --- this function reference is not an `instance` instance when it's invoked by a promise. – zerkms Feb 10 '15 at 02:22
  • @minitech: What you are proposing creates a lot of boilerplate code, in particular for long promise chains. And like all boilerplate, it is quite brittle: I (or someone else) forgets or accidentally deletes a "resolve/bind" and I spend half an hour tracking down a weird bug. This is why I like the "pre-bound" version. However, my personal preference doesn't matter (and neither does yours). I am just curious if the new standard broke something that used to work. – Holger Feb 10 '15 at 02:25
  • @Holger it worked by accident - you do not use instance method in your code. If you want "something similar" - move `doSomething` out of constructor/class, since that's what you have in your first snippet. – zerkms Feb 10 '15 at 02:34
  • @zrkms: Well, not really - the nice thing is that the constructor conveniently provides a (lexical) scope for the "instance" variables (I agree they aren't instance variables in the technical sense but logically, they are). I guess I could reformulate my question title: how do I create & access the (lexical) scope of a class instance? – Holger Feb 10 '15 at 03:06
  • you can't really export lexical scope. it's lexical after all. – the8472 Feb 10 '15 at 10:09
  • 1
    Your question title is seriously misleading. You can "achieve object encapsulation" in many ways, including using ES6 classes--that's what classes do for a living. What you really mean is "Do ES6 classes really mean I can't keep using my trick of pre-binding methods by referring to `this` inside them through a local copy in the constructor?" That was never a particularly interesting pattern to begin with IMHO. For one thing, it places the method in every single instance, instead of in the prototype. –  Feb 10 '15 at 13:59
  • By the way, for `new Promise(resolve => resolve(instanceES6))`, more idiomatic is just `Promise.resolve(instanceES6)`. –  Feb 10 '15 at 14:03
  • 1
    What you are asking for has nothing to do with ES6 classes. In the first case you are declaring the method inside the constructor, in the second case on the prototype. Of course these will yield different results (because lexical scope), but that's independent of ES6 classes. You could also define the method inside `constructor` if you wanted to. – Felix Kling Feb 10 '15 at 16:30
  • @Holger: It doesn’t tend to. You may want to look into how to use promises (not as callbacks) more. Related to this; I can’t seem to find a closer example at the moment. https://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it – Ry- Feb 10 '15 at 17:30
  • @torazaburo classes ensure encapsulation in languages with a class system (Java, C#, C++ - the weird exception being Python). To me, part of encapsulation is keeping private state private. I don't consider state that anybody can inject (i.e., bind) from the outside "private". To be honest, I never liked "my trick" either which is why I was hoping that ES6 would provide a way of getting rid of it. I am looking for one of the "many ways" you mention that truly encapsulates my state. The question could also be: how can an instance method get a reference to the object the constructor was bound to? – Holger Feb 11 '15 at 02:01
  • Well, there was a lengthy discussion about ES6 classes and for a variety of reasons we ended up where we are now, with no private variables, unless you count the semi-privacy provided by using Symbol's as keys. As Felix mentioned, you still define any local variables you want in your constructor, and then close around them in methods defined within your constructor, granted not a very elegant solution. At the same time, no one is forcing you to use ES6 classes, and many people say you shouldn't, so you can stay with or go back to whatever idioms you have come to love. –  Feb 11 '15 at 03:48

1 Answers1

0

Replace

new Promise(resolve => resolve(instanceES6)).then(instanceES6.doSomething);

with

new Promise(resolve => resolve(instanceES6)).then(_ => _.doSomething());

It's shorter and achieves what you want with the available tools.

If that is, as you mention in the comments, too brittle for you preferences you could replace the .then with a custom function with takes a function from the prototype, checks that the instance has it (by using the function.name property for the reverse lookup + a reference identity check) and then invokes it on the instance. Basically adding runtime type checking to the promise chain.

Since javascript has no method reference :: operator like java8 we'll have to alias the prototype for that:

class SomethingES6 {
  ...
}

const SomethingClass = SomethingES6.prototype

...

new Promise(...).checkedThen(SomethingClass.doSomething);

Where .checkedThen is the custom function with typechecking. Since extending built-ins is bad practice one could also subclass Promise first or compose it functionally by importing the custom then into the current scope:

then(then(new Promise(...), SomethingClass.doSomething), SomethingClass.doSomethingElse)

the8472
  • 40,999
  • 5
  • 70
  • 122
  • 1
    In my opinion, this is a bad solution to a non-existent problem. –  Feb 10 '15 at 17:15
  • The very first suggestion I made was simply using lambdas. Everything beyond that is more of an "if you still insist" answer. – the8472 Feb 10 '15 at 17:43