forforf
forforf

Reputation: 925

Node.js: Workaround for calling methods when an object isn't done initializing?

I'm working on upgrading a library that looks something like this (in coffeescript for conciseness):

//machine.coffee

exports.Machine = class Machine
  constructor: (config) ->
    /** initialize Machine with config data **/

  getSomething: (callback) ->
    /** do something that depends on config **/
    callback null, something     

and is used like you might expect:

machine = require 'machine'
config = {knob: "tweak", switch: "fiddle" }

myMachine = machine.createMachine config

myMachine.getSomething (err, something) ->
  /** we now have something **/

However, now the config is dependent upon a setting that I have to request from another site, so it looks more like this, and Machine was updated to parse and use the data from external sources. So accessing Machine like this works:

config = {knob: "tweak", switch: 'http://returns/switch/data' }

/**methods now must wait until Machine is initialized with remote config data **/
machine.createMachine config, (err, machine) ->
  machine.getSomething (err, something) ->
     /** we now have something **/

But the problem is this breaks the original usage which was:

myMachine = machine.createMachine config

/** remote calls to config not completed yet **/

myMachine.getSomething (err, something) ->
  /** we don't have something because Machine is still initializing **/

My question: Is there anyway to preserve the original use case? One approach might be to queue method calls on the uninitialized class until its fully initialized, but I'm not sure how to even begin doing that. Is there even a way to queue methods? Or are there other better approaches that would allow legacy code with the original usage to still work?

Clarification: I have no control over the initial config data nor over when or what methods will be subsequently called on Machine. I do have the ability to modify Machine in any way necessary though.

Upvotes: 0

Views: 611

Answers (3)

Chris Subagio
Chris Subagio

Reputation: 6339

I'm not sure I follow the problem. getSomething() is already async, right? So presumably the implementation of getSomething() is already deferred and returns execution.

If, otoh, you don't have an internal deferral mechanism, i.e. you were previously relying on deferral being provided by another library like a database driver that gets built in your constructor, then you'll need to build your own deferral mechanism.

The simplest form I can think of, off the top of my head would be something like:

exports.Machine = class Machine
  constructor: (config) ->
    @deferred = []
    initialize (err, @magic) => 
      @ready = true
      def[0].apply( @, def[1] ) for def in @deferred

  getSomething: (callback) ->
    if @ready
      @magic.getSomething (err, stuff) -> callback( stuff )
    else
      @deferred.push [@getSomething, arguments]

In a nutshell: if you can't do it yet, remember what it is you were trying to do in a log, and then just return. Later, when you're ready to do those things, loop through the log and go back to where you were, to pick up where you left off.

Upvotes: 1

Domenic
Domenic

Reputation: 112857

I don't think you should expose a Machine constructor anymore. Rather, you should expose an async factory function:

class Machine # not exported
    # ...

exports.createMachine = (config, cb) ->
    getConfig(config, (err, configData) ->
        if err return cb(err)

        cb(new Machine(configData))
    )

Upvotes: 1

Eric Bock
Eric Bock

Reputation: 1782

You could pull the remote calls out into a function that fills config. Then you could move machine.createMachine config and the getSomething call into a callback that gets called after config is completely filled out.

That way your machine doesn't have to know anything about the remote sources or how to parse their data.

Upvotes: 1

Related Questions