Search code examples
javascriptnode.jses6-promisees6-modules

How to export from within an imported module's Promise?


I'm trying to work around the fact that I need to import a pure ESM package into a non-module. I can't change that fact about the script.

The workaround I'm trying to use is the import() function (a "dynamic import"). That returns a Promise instead of the actual module. I can't use await since I'm not in a module, so instead I'm using .then().

The pure ESM package (unist-util-visit) is used in a function exported by my script, which is then used in another script. So the import chain goes:

importer.js imports imported.js imports unist-util-visit

So the issue is that anything I export from within the .then() function in imported.js does not show up in importer.js.

And it's not even a timing issue. I used an EventEmitter to make importer.js wait until imported.js's .then() is done executing:

imported.js:

const EventEmitter = require('events');

module.exports.emitter = new EventEmitter();
module.exports.outsideFxn = function () {
  console.log('hello');
}
import('unist-util-visit').then((unistUtilVisit) => {
  module.exports.fxn = function() {
    console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
  }
  module.exports.emitter.emit('ready');
});

importer.js:

import('./imported.js').then((imported) => {
  console.log("In importer.js's .then():");
  console.log('  fxn:', imported.fxn);
  console.log('  outsideFxn:', imported.outsideFxn);
  imported.emitter.on('ready', () => {
    console.log("After imported.js is done:")
    console.log('  fxn:', imported.fxn);
  });
});

When I execute it, this is the output:

$ node importer.js 
In importer.js's .then():
  fxn: undefined
  outsideFxn: [Function (anonymous)]
After imported.js is done:
  fxn: undefined

What am I missing? Why are no exports being defined in the .then() function? How can I get my function exported?


Solution

  • Instead of

    import('unist-util-visit').then((unistUtilVisit) => {
      module.exports.fxn = function() {
        console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
      }
      module.exports.emitter.emit('ready');
    });
    

    where you attempt to modify your module's exports after it has probably been consumed by dependents, why not export a promise that yields the function when it completes?

    module.exports.fxnP = 
        import('unist-util-visit')
          .then((unistUtilVisit) => () => { 
              console.log(`unistUtilVisit: ${typeof unistUtilVisit}`); 
          });
    

    Now you consume it:

    import('./imported.js').then((imported) => {
        imported.fxnP.then((fxn) => {
            fxn();
        });
    });
    

    or, more neatly::

    import('./imported.js')
        .then(({fxnP}) => fxnP)
        .then((fxn) => fxn());