Search code examples
javascriptnode.jsasynchronousexport

NodeJS, JS how to export Promise value in a module, not the function?


Let's say I want to export in a single JS module some value which is obtained by calling some async function. What is the mechanism to make the export wait for the result/Promise to be resolved?

As an example code snippet I put this here

function go() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("Success!"), 3000);
  });
}

let AS;
go().then((x) => {
  AS = x;
});
module.exports = AS;

This function could make any API request. I don't want to export the entire function and call it in other module.


Solution

  • Two answers for you:

    • With standard JavaScript modules (ESM)
    • With CommonJS (CJS), the module system you're using in the example in the question

    With Standard JavaScript Modules (ESM)

    Now that support for ESM has matured, in most Node.js projects you can switch to ESM from CommonJS by putting "type": "module" in your package.json and changing your require() calls to import statements as necessary. If you can do that, you can use top-level await. With top-level await, you can make your module execution wait for a promise to settle (which makes modules that import from your module wait until the promise settles). So you'd do this:

    function go() {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve("Success!"), 500);
        });
    }
    
    export const AS = await go();
    

    That exports a named export, but you could also replace that last line with this to do a default export (which is more similar to what you have in your CommonJS code; I prefer to avoid default exports):

    export default await go();
    

    Modules using your module don't have to be aware of the fact that the value comes from an asynchronous source; they aren't evaluated until your module evaluation has finished (after the promise settles). They just import as usual:

    import { AS } from "./promise.js"; // If it's a named export
    // import AS from "./promise.js";  // If it's the default export
    console.log("AS = " + AS);
    

    With CommonJS (CJS)

    With CommonJS, your best bet is to export the promise. That way, code using your module has a standard way to handle the fact that the value may not be available yet — Consuming the promise:

    require("./your-moudule")
    .then(AS => {
        // ...use `AS` here...
    })
    .catch(error => {
        // ...handle the fact we didn't get it here...
    });
    

    But if you want to export the value instead, you can, it's just usually not your best approach. You'd do that by exporting an object and then updating its AS property:

    function go() {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve("Success!"), 500);
        });
    }
    
    module.exports = {AS: undefined};
    go().then((x) => {
        module.exports.AS = x;
    });
    

    Modules using your module would have to deal with the fact that briefly (probably) during process startup, they'll be getting undefined. Here's code using the module above:

    const mod = require("./promise"); // Note: Not destructuring at this point
    const timer = setInterval(() => {
        const { AS } = mod;
        console.log("AS = " + AS);
        if (AS) {
            clearInterval(timer);
        }
    }, 100);
    

    If you run that, you'll see AS = undefined ~5 times and then AS = Success!.

    In both cases, it makes the code using your module more complex (they have to await the promise or loop when they see undefined). ESM's approach is simpler from that perspective (those modules just don't run until your module is ready).