Reputation: 925
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
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
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
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