3

I am writing a constructor in JavaScript that has the following properties:

function WhizBang() {

   var promise;

   this.publicMethod_One = function publicMethod_One() { ... };
   this.publicMethod_Two = function publicMethod_Two() { ... };

   promise = asyncInit();
}

So, calling new WhizBang() will start the asyncInit() process. What isn't obvious from the code above is that none of the public methods in the interface should run until this call to asyncInit() has closed.

So, the definition of publicMethod_One() might look something like this:

function publicMethod_One() {

  promise
    .then( doStuff )
    .catch( reportInitFailure )
  ;

  function doStuff() { ... }
  function reportInitFailure() { ... }
}

Some of the things that happen in doStuff() are asynchronous; some of them aren't.

So, if an end user of my class did something like this:

function main() {

  var whizBang = new WhizBang();

  whizBang
    .publicMethod_One()
    .publicMethod_Two()
  ;
}

The call to publicMethod_One() musn't be made until asyncInit() has closed. And the call to publicMethod_Two() musn't be made until both asyncInit() and publicMethod_One() have closed.

How can I define my class methods so that they are chainable?

What I think I need to do is define a class whose public methods are all equivalent to calling then() on a promise, followed by class specific, implementation stuff.

Internets, halp!

( Bonus points for using the Bluebird Promise Library in your answer. )

  • 1
    Constructors that perform IO generally make me dizzy. I'd separate object creation and IO if possible. – Benjamin Gruenbaum Feb 06 '15 at 22:53
  • 1
    See the second half of [this answer](http://stackoverflow.com/questions/28227480/javascript-async-constructor-pattern/28228236#28228236) for why it's a generally bad idea to run async operations in constructors. What you need to do is to return a promise for your async operation so that caller can know when it's done, but you can't return a promise from the constructor because the constructor has to return the object. Much better to have an `.init()` method that returns a promise does the async part of the initialization and then you can wait on that promise before doing other stuff. – jfriend00 Feb 06 '15 at 23:00
  • 1
    related: [Is it bad practice to have a constructor function return a Promise?](http://stackoverflow.com/q/24398699/1048572) – Bergi Feb 07 '15 at 19:33

1 Answers1

4

What you have now

What you have now is actually pretty nice. Since you're caching the result of asyncInit in a promise and everyone waits for the same promise - it's impossible for any of the code in those functions to run before the promise has finished.

function publicMethod_One() {    
  promise // the fact you're starting with `promise.` means it'll wait
    .then( doStuff )
    .catch( reportInitFailure );
}

So rather than forcing people to wait in order to use publicMethod_One they already can call it right away and the methods like doStuff will only execute the promise has resolved.

A fluid interface

Well, like you noticed your code has a major issue, there is no way for a given method to know when to run or a way to sequence methods - you can solve this in two ways:

  • Create a fluid interface that returns this on every action and queues the promise.
  • Return the promise from each async call.

Let's look at the two approaches:

Fluid interface

This means that all your methods return the instance, they must also queue so things don't happen 'at once'. We can accomplish this by modifying promise on each call:

function publicMethod_One() {    
  promise = promise // note we're changing it
    .then( doStuff )
    .catch( reportInitFailure );
  return this; // and returning `this`
}

You might also want to expose a .ready method that returns the current promise so the sequence of operations can be waited for from the outside:

function ready(){
    return this.promise;
}

This can enable things like:

var ready = new WhizBang().publicMethod_One().publicMethod_One().ready();
ready.then(function(){
     // whizbang finished all operations
}); // can also add error handler

Returning thenables

In my opinion this is the simpler approach, all your methods return the promise they create so they can individually be waited for:

function publicMethod_One() {    
  return promise // note we're returning and not changing it
    .then( doStuff )
    .catch( reportInitFailure );
}

This is nice because the async operation is exposed outside.

Chaining is possible since you're using bluebird with .bind as such:

var whiz = new WhizBang();
var res = Promise.bind(whiz).then(whiz.publicMethod_one)
                            .then(whiz.publicMethod_one);
res.then(function(){
     // all actions completed, can also use return values easier.
});

But the advantage is it easier to reason about from the outside - for this reason I prefer this approach personally. Personally I always prefer to return meaningful data than to change internal state.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504