290
votes

I have a simplified function that looks like this:

function(query) {
  myApi.exec('SomeCommand', function(response) {
    return response;
  });
}

Basically i want it to call myApi.exec, and return the response that is given in the callback lambda. However, the above code doesn't work and simply returns immediately.

Just for a very hackish attempt, i tried the below which didn't work, but at least you get the idea what i'm trying to achieve:

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  while (!r) {}
  return r;
}

Basically, what's a good 'node.js/event driven' way of going about this? I want my function to wait until the callback gets called, then return the value that was passed to it.

11
Or am i going about it entirely the wrong way here, and should i be calling another callback, rather than returning a response?Chris
This is in my opinion the best SO explanation why the busy loop doesn't work.bluenote10
Don't try to wait. Just call next function(callback-dependent) within at the end of callback itselfAtul

11 Answers

297
votes

The "good node.js /event driven" way of doing this is to not wait.

Like almost everything else when working with event driven systems like node, your function should accept a callback parameter that will be invoked when then computation is complete. The caller should not wait for the value to be "returned" in the normal sense, but rather send the routine that will handle the resulting value:

function(query, callback) {
  myApi.exec('SomeCommand', function(response) {
    // other stuff here...
    // bla bla..
    callback(response); // this will "return" your value to the original caller
  });
}

So you dont use it like this:

var returnValue = myFunction(query);

But like this:

myFunction(query, function(returnValue) {
  // use the return value here instead of like a regular (non-evented) return value
});
87
votes

One way to achieve this is to wrap the API call into a promise and then use await to wait for the result.

// let's say this is the API function with two callbacks,
// one for success and the other for error
function apiFunction(query, successCallback, errorCallback) {
    if (query == "bad query") {
        errorCallback("problem with the query");
    }
    successCallback("Your query was <" + query + ">");
}

// myFunction wraps the above API call into a Promise
// and handles the callbacks with resolve and reject
function apiFunctionWrapper(query) {
    return new Promise((resolve, reject) => {
        apiFunction(query,(successResponse) => {
            resolve(successResponse);
        }, (errorResponse) => {
            reject(errorResponse);
        });
    });
}

// now you can use await to get the result from the wrapped api function
// and you can use standard try-catch to handle the errors
async function businessLogic() {
    try {
        const result = await apiFunctionWrapper("query all users");
        console.log(result);
        
        // the next line will fail
        const result2 = await apiFunctionWrapper("bad query");
    } catch(error) {
        console.error("ERROR:" + error);
    }
}

// call the main function
businessLogic();

Output:

Your query was <query all users>
ERROR:problem with the query
24
votes

check this: https://github.com/luciotato/waitfor-ES6

your code with wait.for: (requires generators, --harmony flag)

function* (query) {
  var r = yield wait.for( myApi.exec, 'SomeCommand');
  return r;
}
12
votes

If you don't want to use call back then you can Use "Q" module.

For example:

function getdb() {
    var deferred = Q.defer();
    MongoClient.connect(databaseUrl, function(err, db) {
        if (err) {
            console.log("Problem connecting database");
            deferred.reject(new Error(err));
        } else {
            var collection = db.collection("url");
            deferred.resolve(collection);
        }
    });
    return deferred.promise;
}


getdb().then(function(collection) {
   // This function will be called afte getdb() will be executed. 

}).fail(function(err){
    // If Error accrued. 

});

For more information refer this: https://github.com/kriskowal/q

10
votes

If you want it very simple and easy, no fancy libraries, to wait for callback functions to be executed in node, before executing some other code, is like this:

//initialize a global var to control the callback state
var callbackCount = 0;
//call the function that has a callback
someObj.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});
someObj2.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});

//call function that has to wait
continueExec();

function continueExec() {
    //here is the trick, wait until var callbackCount is set number of callback functions
    if (callbackCount < 2) {
        setTimeout(continueExec, 1000);
        return;
    }
    //Finally, do what you need
    doSomeThing();
}
6
votes

Since node 4.8.0 you are able to use the feature of ES6 called generator. You may follow this article for deeper concepts. But basically you can use generators and promises to get this job done. I'm using bluebird to promisify and manage the generator.

Your code should be fine like the example below.

const Promise = require('bluebird');

function* getResponse(query) {
  const r = yield new Promise(resolve => myApi.exec('SomeCommand', resolve);
  return r;
}

Promise.coroutine(getResponse)()
  .then(response => console.log(response));
5
votes

Note: This answer should probably not be used in production code. It's a hack and you should know about the implications.

There is the uvrun module (updated for newer Nodejs versions here) where you can execute a single loop round of the libuv main event loop (which is the Nodejs main loop).

Your code would look like this:

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  var uvrun = require("uvrun");
  while (!r)
    uvrun.runOnce();
  return r;
}

(You might alternative use uvrun.runNoWait(). That could avoid some problems with blocking, but takes 100% CPU.)

Note that this approach kind of invalidates the whole purpose of Nodejs, i.e. to have everything async and non-blocking. Also, it could increase your callstack depth a lot, so you might end up with stack overflows. If you run such function recursively, you definitely will run into troubles.

See the other answers about how to redesign your code to do it "right".

This solution here is probably only useful when you do testing and esp. want to have synced and serial code.

1
votes

supposing you have a function:

var fetchPage(page, callback) {
   ....
   request(uri, function (error, response, body) {
        ....
        if (something_good) {
          callback(true, page+1);
        } else {
          callback(false);
        }
        .....
   });


};

you can make use of callbacks like this:

fetchPage(1, x = function(next, page) {
if (next) {
    console.log("^^^ CALLBACK -->  fetchPage: " + page);
    fetchPage(page, x);
}
});
1
votes

It's 2020 and chances are the API has already a promise-based version that works with await. However, some interfaces, especially event emitters will require this workaround:

// doesn't wait
let value;
someEventEmitter.once((e) => { value = e.value; });
// waits
let value = await new Promise((resolve) => {
  someEventEmitter.once('event', (e) => { resolve(e.value); });
});

In this particular case it would be:

let response = await new Promise((resolve) => {
  myAPI.exec('SomeCommand', (response) => { resolve(response); });
});

Await has been in new Node.js releases for the past 3 years (since v7.6).

0
votes

That defeats the purpose of non-blocking IO -- you're blocking it when it doesn't need blocking :)

You should nest your callbacks instead of forcing node.js to wait, or call another callback inside the callback where you need the result of r.

Chances are, if you need to force blocking, you're thinking about your architecture wrong.

0
votes

Using async and await it is lot more easy.

router.post('/login',async (req, res, next) => {
i = await queries.checkUser(req.body);
console.log('i: '+JSON.stringify(i));
});

//User Available Check
async function checkUser(request) {
try {
    let response = await sql.query('select * from login where email = ?', 
    [request.email]);
    return response[0];

    } catch (err) {
    console.log(err);

  }

}