Factory reran on each require or factory's return value cached?

40 views
Skip to first unread message

Daniel Dotsenko

unread,
Jan 26, 2013, 6:08:11 PM1/26/13
to amd-im...@googlegroups.com
Hi.

Is factory fn rerun on each require/define requiring that module or factory's return value cached from first run and returned on all consecutive requests for the module?

This question from stack somehow suggests to me that factory reruns in his particular case (and clobbers a var ref that was originally stuffed into a closure):


My question itself in only tangential to the question on the stack. My question is mostly about what the various AMD loaders do: rerun the factory or reuse prior factory run's return value.

Daniel

John Hann

unread,
Jan 28, 2013, 8:58:48 AM1/28/13
to amd-im...@googlegroups.com
The factory should get run once and only once.

Richard Backhouse

unread,
Jan 28, 2013, 9:01:47 AM1/28/13
to amd-im...@googlegroups.com
John is correct. The spec (https://github.com/amdjs/amdjs-api/wiki/AMD) currently states :

" The third argument, factory, is a function that should be executed to instantiate the module or an object. If the factory is a function it should only be executed once"
--
You received this message because you are subscribed to the Google Groups "amd-implement" group.
To unsubscribe from this group and stop receiving emails from it, send an email to amd-implemen...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Ben Hockey

unread,
Jan 28, 2013, 10:31:42 AM1/28/13
to amd-im...@googlegroups.com
it's an interesting question and i think saying that the factory should only get run once is an oversimplified answer.  what should happen if 2 module ids translate to the same path?  in that case i think the factory should be run a 2nd time because this is a 2nd module (even though the path is the same).  so, maybe a more complete answers is that the factory should be run once per normalized module id.  this would allow a loader to cache modules based on paths and run the factory again if another module id happens to resolve to the same path.

as an example that demonstrates why the factory should be run a 2nd time (for a 2nd distinct normalized module id), consider the case where a module might provide a pub/sub interface.  it's simple enough to setup a configuration where 2 normalized module ids (eg "hub" and "pubsub") map to the same path and then consider that one module/package consumes "hub" and the other consumes "pubsub".  each consumer may have an expectation that it has sole access to the pub/sub module and so subscribing and publishing to the "fire" topic should be safe.  if you give both modules the same instance of the module providing the pub/sub interface then it's potentially going to break the consumers.

...and what should happen to mapped modules?  i tend to think that they should share the single instance of the normalized id they correspond to.

this would allow a combination of paths and map to be used to control if modules get a shared instance of a dependency or their own copy.  the snippet below is meant to be a configuration that would give "ui" it's own instance of the pubsub interface and "clientA" and "clientB" would get a shared instance (my config might be wrong but hopefully demonstrates what i'm thinking)

// 2 normalized module ids map to the same path
paths: {
  pubsubForUi: "/path/to/pubsub",
  sharedPubSub: "/path/to/pubsub"
},
map {
  // ui uses a separate instance of the pubsub interface
  ui: {
    "pubsub": "pubsubForUi"
  },
  // clientA and clientB use a shared instance of the pubsub interface
  clientA: {
    "pubsub": "sharedPubSub"
  },
  clientB: {
    "pubsub": "sharedPubSub"
  }
}

i don't know if we need to expand the spec to call out this kind of thing but i'm interested to hear some thoughts about this idea.

thanks,

ben...

John Hann

unread,
Jan 28, 2013, 11:55:18 AM1/28/13
to amd-im...@googlegroups.com
Hey Ben,

I agree with you.  The factory *should* run for each uniquely-resolved module id.  My answer (and the spec) are obviously aimed at the simple case.  (Atm, curl.js only runs it once, but I've been wanting to fix this for a while.)

If we can get agreement by all parties, we should make an effort to change the language of the spec.  Hopefully, we can clarify it without making it overly complicated for newbs to understand.

-- John


--
You received this message because you are subscribed to the Google Groups "amd-implement" group.
To unsubscribe from this group, send email to amd-implemen...@googlegroups.com.

Jakob Heuser

unread,
Jan 29, 2013, 2:03:55 PM1/29/13
to amd-im...@googlegroups.com
I feel like having a factory run once (and only once) is necessary. It allows for proper encapsulation. In the below example, the author of MyModule will run into problems due to their factory being ran multiple times, something the MyModule author would have zero control over.

// MyModule.js
define('MyModule', function() {
  var privateVariable = 1;
  var myClass = function() {
    privateVariable++;
  };
  myClass.prototype.varIs = function() {
    return privateVariable;
  }

  return myClass;
});


// calling code that results in two define calls,
// each requires MyModule. (The MyModuleRemapped
// is normalized to MyModule)
define(['MyModule'], function(MyModule) {
  var m = new MyModule();
  console.log(m.varIs()); // 2
});
define(['MyModuleRemapped'], function(MyModule) {
  var m = new MyModule();
  console.log(m.varIs()); // 2 (expected: 3)
});

I believe this would break module authors' expectations.

--Jakob

John Hann

unread,
Jan 29, 2013, 2:22:51 PM1/29/13
to amd-im...@googlegroups.com
Hey Jakob,

You're absolutely right.  In the "normal" case, this is exactly what should happen.  However, it's possible that the module author intends singleton-like behavior only within a certain scope.  This scope could be controlled by the end user and managed by the AMD loader if the loader executed the factory for any [mapped] module id that maps to the same [singleton] module.  Ben shows a possible use case for this.  

If people feel strongly that we should *never* allow a factory to run multiple times, I wouldn't lose sleep.  It feels like something that 99% of devs won't encounter and can be worked around fairly easily by the module author.

-- John



--
You received this message because you are subscribed to the Google Groups "amd-implement" group.
To unsubscribe from this group and stop receiving emails from it, send an email to amd-implemen...@googlegroups.com.

Ben Hockey

unread,
Jan 29, 2013, 2:28:43 PM1/29/13
to amd-im...@googlegroups.com
Jakob,

this is why i specifically called out paths and map to be handled differently.  my goal is to give complete control over the circumstances that would cause a factory to be run more than once.  there are cases where a shared copy of a module is expected and there are also equally valid cases where distinctly separate instances of a module would be expected.  only the person putting all the pieces together has the necessary information to know if a module's dependency should be shared (factory is run once) or unique (factory is run multiple times).  

in your example, you're saying that you want a shared instance but in one module this instance is identified as 'MyModule' and in another module this instance is identified as 'MyModuleRemapped'.  in what i'm proposing, your use case would be supported by using map to translate 'MyModuleRemapped' to 'MyModule' for the module with that dependency.  using map is your signal to the loader that you want a shared instance.  the mapped module id normalizes to 'MyModule' and so that means it would be shared with all other modules that have a similar dependency that normalizes to 'MyModule'

if on the other hand 'MyModuleRemapped' needed to be a unique instance then you would have used paths to configure the loader to get a new instance of the module.  The normalized id would be 'MyModuleRemapped' but the path would resolve to the same URL as the module with the normalized 'MyModule' id.  the key here is that 2 distinctly different normalized module ids is an indication to the loader that there should be 2 distinctly different instances of a module even if they resolve to the same path/URL.

this gets to be a little bit of juggling if you have 2 modules that declare a dependency on 'MyModule' but want unique instances.  however, i think that's probably the less common case and it is achievable so at least it can be done even if it's extra config

paths: {
  MyModuleForA: '/path/to/my/module',
  MyModuleForB: '/path/to/my/module'
},
map: {
  A: {
    MyModule: 'MyModuleForA'
  },
  B: {
    MyModule: 'MyModuleForB'
  }
}

ben...

John Hann

unread,
Jan 29, 2013, 2:34:57 PM1/29/13
to amd-im...@googlegroups.com
Sorry, I didn't see the "MyModuleRemapped" part. -- J


--

Daniel D.

unread,
Jan 29, 2013, 3:15:05 PM1/29/13
to amd-im...@googlegroups.com
+1 Ben, John. 

Since a "module" is linked to its normalized ID, explicitly asking for different ID somehow infers different resource - rerunning factory is somehow justified.


I especially like Ben's example of PubSub as an example of need to get a different instance. However feel "maps" or "paths" to be a poor substitude of having a 2 separate defines linking to module defining the PubSub class.

// contents of pubsub.js
define(factory)

// contents of pubsub_a.js
define(['pubsub'], function(PubSubClass){
return new PubSubClass()
})

// contents of pubsub_b.js
define(['pubsub'], function(PubSubClass){
return new PubSubClass()
})

This way you can push pubsub sub- instantiator into actual feature folder and keep the config file simple(er) and sane(er). 

Somehow I feel "single run only" crowd will need an overwhelmingly vivid example to be truly sold on it. :)

Daniel

Ben Hockey

unread,
Jan 29, 2013, 3:28:04 PM1/29/13
to amd-im...@googlegroups.com

On Tuesday, January 29, 2013 2:15:05 PM UTC-6, Daniel Dotsenko wrote:
+1 Ben, John. 

Since a "module" is linked to its normalized ID, explicitly asking for different ID somehow infers different resource - rerunning factory is somehow justified.


I especially like Ben's example of PubSub as an example of need to get a different instance. However feel "maps" or "paths" to be a poor substitude of having a 2 separate defines linking to module defining the PubSub class.

// contents of pubsub.js
define(factory)

// contents of pubsub_a.js
define(['pubsub'], function(PubSubClass){
return new PubSubClass()
})

// contents of pubsub_b.js
define(['pubsub'], function(PubSubClass){
return new PubSubClass()
})

this approach is fine if your pubsub module returns a constructor.  of course, this is not always under our control and might not be the case.  for example:

define([], function () {
  // a map of topic names to handlers
  var topics = {};

  return: {
    publish: function (topic) { ... },
    subscribe: function (topic, handler) { ... }
  };
});

in this case the module uses a singleton pattern.  in some cases we will want all consumers to share this instance so that they can communicate with each other via a common dependency.  however, in other cases some group of consumers may expect that they are the only ones consuming this module and so we need a way to provide another instance of this module to them.  that's the reasoning for the semantics i'm suggesting for map and paths - modules may contain state and might not always return constructors.

ben...

Jakob Heuser

unread,
Jan 29, 2013, 6:01:49 PM1/29/13
to amd-im...@googlegroups.com
Ben,

I guess coming from a CommonJS background, I am bringing with me some expectations. On a filesystem, an analogous map/path implementation would be to symlink two files. I've put together a gist at https://gist.github.com/4668680 that illustrates what happens in node. While I don't mind if AMD behaves differently, that adds cognitive load for AMD developers and people trying to use code on both client and browser. Ultimately, I'm okay with either direction, we'll just need to be really clear in the spec.

We've been talking pretty exclusively about factories as methods. AMD also supports factories as object literals. I'm a bit stumped on how we would ensure uniqueness of the objects inside of exports.*. The only working solution I can think of is to wrap object literals in a function (at the AMD library layer) so they behave more like factories as methods. John, can you share what curl.js would be doing to provide the uniqueness?

--Jakob

Ben Hockey

unread,
Jan 29, 2013, 6:38:12 PM1/29/13
to amd-im...@googlegroups.com
jakob,

a file system is a good analogy to use... but it might not be as you expect.  map and path operate at different levels of the analogy.

a symlink is analogous to using map... with a symlink you're using a different handle to the same logical entity.  if you change the contents of a file via the symlink handle, the original changes too - one is just a reference to the other.  

to extend the analogy, paths in AMD is more analogous to blocks on your disk and module ids are analogous to file names.  if you had a file system with deduplication (eg ZFS) then if you copy a file to another destination, 2 distinct files will show up in the file system but they might exist at the same blocks on the disk (since they share exactly the same bytes). that's abstracted from you as the user of the file system because as far as you are concerned they are 2 different files.  even if these 2 files can be found at the same blocks on the disk, the filesystem needs to present them to you as separate entities.  i'm sure that if nodejs was presented with this scenario it would treat each file as separate entities.

in AMD, module ids are a layer of abstraction that (mostly) hide the path/URL of the module itself.  so, if 2 module ids map to the same path, the loader still needs to present those to you as distinct entities because they have unique ids.

btw, your example of an object literal is an interesting (and very valid) one that really throws a wrench in the works :)

ben...

James Burke

unread,
Jan 30, 2013, 5:00:31 PM1/30/13
to amd-im...@googlegroups.com
On Mon, Jan 28, 2013 at 7:31 AM, Ben Hockey <neonst...@gmail.com> wrote:
> it's an interesting question and i think saying that the factory should only
> get run once is an oversimplified answer. what should happen if 2 module
> ids translate to the same path?

If two modules point to the same path, this should indicate that both
modules are in that file (like a built file). They each have their own
named define in that case. If "map" is in play, it is just like you
said later, a symlink.

So, I do not think there are cases where two module IDs would result
in the same file being loaded twice, giving two different values.

James
Reply all
Reply to author
Forward
0 new messages