Asked  6 Months ago    Answers:  5   Viewed   2.1k times

I'm kinda new to module creation and was wondering about module.exports and waiting for async functions (like a mongo connect function for example) to complete and exporting the result. The variables get properly defined using async/await in the module, but when trying to log them by requiring the module, they show up as undefined. If someone could point me in the right direction, that'd be great. Here's the code I've got so far:

// module.js

const MongoClient = require('mongodb').MongoClient
const mongo_host = '127.0.0.1'
const mongo_db = 'test'
const mongo_port = '27017';

(async module => {

  var client, db
  var url = `mongodb://${mongo_host}:${mongo_port}/${mongo_db}`

  try {
    // Use connect method to connect to the Server
    client = await MongoClient.connect(url, {
      useNewUrlParser: true
    })

    db = client.db(mongo_db)
  } catch (err) {
    console.error(err)
  } finally {
    // Exporting mongo just to test things
    console.log(client) // Just to test things I tried logging the client here and it works. It doesn't show 'undefined' like test.js does when trying to console.log it from there
    module.exports = {
      client,
      db
    }
  }
})(module)

And here's the js that requires the module

// test.js

const {client} = require('./module')

console.log(client) // Logs 'undefined'

I'm fairly familiar with js and am still actively learning and looking into things like async/await and like features, but yeah... I can't really figure that one out

 Answers

87

You have to export synchronously, so its impossible to export client and db directly. However you could export a Promise that resolves to client and db:

module.exports = (async function() {
 const client = await MongoClient.connect(url, {
   useNewUrlParser: true
 });

  const db = client.db(mongo_db);
  return { client, db };
})();

So then you can import it as:

const {client, db} = await require("yourmodule");

(that has to be in an async function itself)

PS: console.error(err) is not a proper error handler, if you cant handle the error just crash

Friday, June 25, 2021
 
the_e
answered 6 Months ago
90

The issue is with

  • how ES6 modules are emulated in CommonJS
  • how you import the module

ES6 to CommonJS

At the time of writing this, no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS. But how exactly does that happen?

Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.

ES6 default exports are actually also named exports, except that default is a "reserved" name and there is special syntax support for it. Lets have a look how Babel compiles named and default exports:

// input
export const foo = 42;
export default 21;

// output
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
var foo = exports.foo = 42;
exports.default = 21; 

Here we can see that the default export becomes a property on the exports object, just like foo.

Import the module

We can import the module in two ways: Either using CommonJS or using ES6 import syntax.

Your issue: I believe you are doing something like:

var bar = require('./input');
new bar();

expecting that bar is assigned the value of the default export. But as we can see in the example above, the default export is assigned to the default property!

So in order to access the default export we actually have to do

var bar = require('./input').default;

If we use ES6 module syntax, namely

import bar from './input';
console.log(bar);

Babel will transform it to

'use strict';

var _input = require('./input');

var _input2 = _interopRequireDefault(_input);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log(_input2.default);

You can see that every access to bar is converted to access .default.

Monday, June 7, 2021
 
EzzDev
answered 6 Months ago
31

Try this in your code:

import Foo from './Foo';
import Bar from './Bar';

// without default
export {
  Foo,
  Bar,
}

Btw, you can also do it this way:

// bundle.js
export { default as Foo } from './Foo'
export { default as Bar } from './Bar'
export { default } from './Baz'

// and import somewhere..
import Baz, { Foo, Bar } from './bundle'

Using export

export const MyFunction = () => {}
export const MyFunction2 = () => {}

const Var = 1;
const Var2 = 2;

export {
   Var,
   Var2,
}


// Then import it this way
import {
  MyFunction,
  MyFunction2,
  Var,
  Var2,
} from './foo-bar-baz';

The difference with export default is that you can export something, and apply the name where you import it:

// export default
export default class UserClass {
  constructor() {}
};

// import it
import User from './user'
Friday, June 11, 2021
 
Student
answered 6 Months ago
25

Your function is not a great example, because the return value is not in any way connected to the await result. Because of this, the use of Promise is irrelevant, as the code could be rewritten without it. Let's change your function a bit, so that it actually generates the result inside the promise:

function sumLater(a, b) {
  return new Promise(resolve => {
    setTimeout(() => resolve(a+b), 1000);
  });
}

async function sum(a, b) {
  // print the numbers
  for (let i = 0; i < 100000; i++) {
    console.log(i);
  }
  // settimeout
  let result = await sumLater(a, b);
  // using the awaited result
  console.log("The awaited result is", result);
  return result;
}

async significantly changes the way the function is executed internally. For one thing, no function using a promise can ever return a value that is generated inside the promise. In the same way, no function that contains await can return anything generated from await onwards without it being wrapped in a promise. The reason for this is the lack of time travel in current computational architectures. If a function could now return a result of an operation that will complete two hours later, it would be a rather impressive breakthrough.

async is as much a signal to the JavaScript engine that the function will be executed in this special way, as well as to human code readers that it returns a Promise, not the returned value (unless the returned value was already a promise in the first place, and did not need wrapping).

Most of your issues are due to a terminological confusion. Don't get hung up on literal meaning of the terminology. Nothing in JavaScript (except Web Workers) is simultaneous, and at the first glance simultaneous and synchronous should be synonyms. In JavaScript, synchronous means "executes entirely within the same task", and asynchronous means "causes a new task to be placed on the execution stack", more or less, as far as I see it. Seen from this angle, async does mean that the function will be asynchronous, for the meaning "asynchronous" has in JavaScript.

Monday, August 16, 2021
 
sohum
answered 4 Months ago
12

You're returning the callback at the moment, which probably doesn't return a Promise - switch the order around, return the Promise that resolves when the sendMessage callback runs. Also note that you should reject if there an err and resolve with the res if there is no err, not the other way around:

class TwilioService {
  sendSms(to, message) {
    return new Promise((resolve, reject) => {               // switch these two lines
      return this.twilio.sendMessage(sms, (err, res) => {   // switch these two lines
        if (err) {
          console.log(err)
          return reject(res) // reject on error, don't resolve
        } else {
          console.log(res, 'ppppppppppppppppp')
          return resolve(res) // resolve when there's no error
        }
      })
    })
  }
}
Wednesday, September 1, 2021
 
Neil Stockton
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share