109

I have code like this:

var client = new mysql.Client(options);
console.log('Icanhasclient');

client.connect(function (err) {
  console.log('jannn');
  active_db = client;
  console.log(err);
  console.log('hest');

  if (callback) {
    if (err) {
      callback(err, null);
    }

    callback(null, active_db);
  }
});

My problem is that Node terminates immediately when I run it. It prints 'Icanhasclient', but none of the console.log's inside the callback are called.

(mysql in this example is node-mysql.

Is there something that can be done to make node.js wait for the callback to complete before exiting?

mikl
  • 23,749
  • 20
  • 68
  • 89
  • 1
    what do you mean prevent exit? Nodejs doesn't exits until all the callbacks are completed. It's a single thread process. – neebz Jun 22 '11 at 16:00
  • 4
    @nEEbz: If that was the case, why does my script exit without executing the callback? – mikl Jun 22 '11 at 16:03
  • It's some issue with connecting to your database; I am not too sure why it's not firing the callback. Does it give any error? – neebz Jun 22 '11 at 16:12
  • No, it just fails silently :( – mikl Jun 22 '11 at 16:35
  • That is very weird. I hope we find an answer to this. – neebz Jun 23 '11 at 07:55
  • I am very late, but for those coming from Google, I've added a one-liner answer: https://stackoverflow.com/a/50451612/938236 – Francisco Presencia May 21 '18 at 15:05
  • The accepted answer is flat out wrong. The command `node -p "(new (require('events'))()).on('never', console.log)"` exits immediately while `node -p "setTimeout(() => console.log('yar'), 6000)"` hangs for six seconds. See [this correct answer](https://stackoverflow.com/a/46916601/1082449) to a similar question. – tiffon Feb 01 '20 at 08:04

8 Answers8

54

Callback is Not Queued

Node runs until all event queues are empty. A callback is added to an event queue when a call such as

  emmiter1.on('this_event',callback).

has executed. This call is part of the code written by the module developer .

If a module is a quick port from a synchronous/blocking version, this may not happen til some part of the operation has completed and all the queues might empty before that occurs, allowing node to exit silently.

This is a sneaky bug, that is one that the module developer might not run into during development, as it will occur less often in busy systems with many queues as it will be rare for all of them to be empty at the critical time.

A possible fix/bug detector for the user is to insert a special timer event before the suspect function call.

george calvert
  • 684
  • 7
  • 5
  • "this may not happen til some part of the operation has completed" - Could you clarify this? Are you saying that node might quit while a piece of code is still running before it has a chance to add a callback to the event queue? – B T Oct 12 '16 at 08:11
  • I guess he means that if the library has converted code that once was synchronous to a new asynchronous version, it's possible that the library creators forgot to queue the callbacks and the event queues get emptied somewhere in the middle of your execution, thus finishing with uncalled callbacks. – Dielson Sales Jul 19 '17 at 20:22
  • 1
    Is there an easy way to reliably reproduce this issue? – Jordan Brough May 24 '18 at 16:18
  • Thanks for this! I had a "hanging" process and didn't understand why it wouldn't quit. After cleaning up all my listeners/event callbacks, the process exited. – ps2goat Jul 25 '18 at 18:45
  • 10
    **WARNING** - Despite having lots of upvotes, I think the info in this answer is incorrect. Calling `EventEmitter.on()` does **not** add anything onto the event loop that Node will wait for (at least in the current version of Node). Seems only things like setting timeouts and making async calls from the core library can do this. You can test this easily by writing a one line program that expects an event that will never fire. From this answer, you'd expect it to never terminate, but it *does* terminate instantly. [See this answer](https://stackoverflow.com/a/46916601/1082449) for more info. – davnicwil Jul 19 '19 at 17:12
48

You can just issue a setTimeout or a recurring timeout with setInterval.

If you want to check for exit conditions, you can also do a conditional timeout:

(function wait () {
   if (!SOME_EXIT_CONDITION) setTimeout(wait, 1000);
})();

Put this at the end of your code and the console will just wait ... and wait ... until you want it to close.

Todd
  • 877
  • 8
  • 5
13

My solution was to instantiate an EventEmitter, and listen for my custom event.

var eventEmitter = new process.EventEmitter();

then I called eventEmitter.emit from the async callback:

client.connect(function (err) {
    eventEmitter.emit('myevent', {something: "Bla"})
});

The last thing in my script was the eventEmitter.on:

eventEmitter.on('myevent', function(myResult){
  // I needed the result to be written to stdout so that the calling process could get it
  process.stdout.write(JSON.stringify(myResult));
});

Node will then wait until the event handler finishes running.

you786
  • 3,659
  • 5
  • 48
  • 74
  • 1
    I am getting ` process.EventEmitter is not a constructor` error. What would you suggest? – Anthony Kong Nov 16 '17 at 02:20
  • Look up how to create an EventEmitter. https://coligo.io/nodejs-event-emitter/ seems like a good resource. It may be that the different versions of Node have changed how to create one. – you786 Nov 16 '17 at 18:05
  • 2
    `EventEmitter` class is defined by the `events` module. So, to create a new emitter: `const EventEmitter = require('events'); var eventEmitter = new EventEmitter()`. – CedX Dec 15 '17 at 19:38
  • 1
    **WARNING** - see my comment on the accepted answer. I think this is wrong, and does not work at least for the current version of node. – davnicwil Jul 19 '19 at 17:17
8

Based on @Todd's answer, I created a one-liner. Include it in the beginning of your script, and set done = true when you are done:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

Example:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

someAsyncOperation().then(() => {
  console.log('Good to go!');
  done = true;
});

How does it work? If we expand it a bit:

// Initialize the variable `done` to `undefined`
// Create the function wait, which is available inside itself
// Note: `var` is hoisted but `let` is not so we need to use `var`
var done = (function wait () {

  // As long as it's nor marked as done, create a new event+queue
  if (!done) setTimeout(wait, 1000);

  // No return value; done will resolve to false (undefined)
})();
Francisco Presencia
  • 8,732
  • 6
  • 46
  • 90
4

Here is my two cents:

async function main()
{
    await new Promise(function () {});
    console.log('This text will never be printed');
}

function panic(error)
{
    console.error(error);
    process.exit(1);
}

// https://stackoverflow.com/a/46916601/1478566
main().catch(panic).finally(clearInterval.bind(null, setInterval(a=>a, 1E9)));
vbarbarosh
  • 3,502
  • 4
  • 33
  • 43
1

Here's how I do it. I make my main entrypoint return a promise, then I use this little wrapper to ensure that as long as that promise isn't settled, node won't exit:

function wrapPromiseMain(entryPoint) {
    const pollTime = 1000000;
    let interval = setInterval(() => {}, pollTime);
    
    return entryPoint().finally(() => {clearInterval(interval)});
}

To use it, just promise-ize your main entry point and pass it as an argument to the wrapper:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

I find it a bit tidier than the basic polling loops because it will auto-cancel the timer when the promise settles, so it doesn't add any additional latency. The polling time can therefore be basically forever if you want it to.

  • Note: edited after posting because I realized I could make it even more concise by using setInterval instead of chained calls to setTimeout. – ad1c371f7d05 Jul 13 '20 at 04:35
-1

I did look at felixge/node-mysql library and didn't see a reference to the command client.connect in the API. Is this the actual call you're trying to make (not trying to be nitpicky here)? Regardless, IMHO you need to think more about how Javascript is designed, because it uses a programming paradigm different than most other popular languages.

The first issue I see in your code is that you haven't defined the callback, so it doesn't actually exist. I'd assume console.log(callback) is undefined. From your code, the anonymous function is the 'callback' for the client.connect function. You have to define what you are calling 'callback' at a higher scope. For example, I will define a function myCallback to exist in the scope higher than the client.connect's anonymous function. It may be useful to lookup Javacscript variable scope.

    var myCallback(err, response) {
      if (err) {
        console.log('err:%s',err);
      } else {
        console.log('response:%s',response);
      }
    }

    client.connect(err, function(response) {
      // this anonymous function is the callback to client.connect, the var
      // 'callback' would be undefined.
      if (err) {
        myCallback(err);
        return; // Explicit call to return, else the lines below would run.
      } 
      myCallback(null, response);
    });

Second, if you do not explicitly call return within Javascript, the function will continue to process. I was bitten by this myself. Finally, Javascript runs an event-driven loop meaning it will never wait for functions to return a value, which is why we have all these callbacks in the first place. You can force Javascript to behave differently, for example by using a while loop until a condition is true. See the 'async' library by caolan, for various strategies of manipulating the event loop. The major disadvantage to overusing these methods is your actually wasting CPU cycles/blocking when you probably should use more callbacks and simply re-think how your programs works.

Community
  • 1
  • 1
exshovelrydr
  • 1,054
  • 8
  • 7
-2

Please try this. Check if this help.

var client = new mysql.Client(options);
console.log('Icanhasclient');
var verbose;

if (!verbose) {
    return new Promise(function (resolve, reject) {
        client.connect(function (err) {
            if (err) {
                console.log(Error in connecting
                SQL ${err}
            )
                ;
                return reject(err);
            }
            verbose = client;
            return resolve(verbose);
        })
    })
} else {
    return new Promise(function (resolve) {
        resolve(verbose);
    })
}
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Tushar
  • 11
  • 1
  • 3