Javascript Basics: Implementing Asynchronous behavior: Callbacks

Learning how to make Javascript perform tasks asynchronously is important for dealing with many routine tasks when writing applications.

Consider the following function code that performs a dns lookup, which doesn't behave as expected when called:

 const urlResolves = passedUrl => {
  let result = 'default';
  dns.lookup(passedUrl, (err, address, family) => {
    if(err){ 
      console.log('error in resolution');
      console.log(err);
      result = false;
      } else {
        console.log(`address is ${address}, family is IPv${family}`);
        result = true;
      }
  });
  console.log(`the result is: ${result}`);
  return result;
}

When I pass a valid hostname to this function, the result returned will almost always be default. However, the message from the successful block will also print the address and family of the passed URL.

This occurs because the function returns before the DNS resolution has completed(and the value of the result variable is modified). In order to sort this behavior, there are a few primary approaches. In this first short post, I'll briefly discuss Callbacks and demonstrate how to perform a dns lookup using them.

This method for dealing with async calls one method on completion of a function, effectively chaining them.

Here is a version of the urlResolves function that takes a callback:

 const urlResolves = (passedUrl, callback) => {
  let result = 'default';
    dns.lookup(passedUrl, (err, address, family) => {
    if(err){ 
      console.log(`error in resolution of ${passedUrl}`);
      result = false;
      callback(err, result);
      } else {
        console.log(`address is ${address}, family is IPv${family}`);
      result = true;
      callback(null, result);
      }
    });
};

You can then call this function and pass an anonymous function as the callback, returning any error as the first argument to the current function's callback (per node convention).

urlResolves(providedUrl, (err, bool) => {
    if(err){
       res.json({error: 'invalid url'});
       return;
    } else {
      console.log('returning json from dns lookup');
      res.json({original_url: req.body.url, short_url: 'placeholder'});
      return;
    }
  });

While useful for solving trivial problems, using callbacks doesn't scale effectively and creates headaches, particularly as you may find that you have to nest them(this is called 'the pyramid of doom').

A 'Promise' is a better option for accomplishing the same thing. A Promise object either results in a success(which is handled by the Javascript callback resolve) or a failure (which is handled by the reject callback). Whatever is passed to the resolve callback will be passed as a parameter in functions chained via then. Here's a function that performs DNS lookups using this approach- it returns a promise, and the address is then passed to the function chained with then.

const doesResolve = url => {
        return new Promise((resolve, reject)=> {
                dns.lookup(url, (err, address, family) => {
                if(err){
                        console.log(err);
                        reject(err);
                } else {
                console.log('lookup successful');
                resolve(address);
                }
                });
        });
}

I wrote a simple wrapper function for console.log to demonstrate the order in which the functions are executed, and then called it following doesResolve:

const logThis = thingToLog => {
        console.log('Initiating logThis function');
        console.log(thingToLog);
}


doesResolve('www.google.com')
        .then(result => logThis(result));

You can chain additional functions with .then, each of which will pass the value returned as a parameter to the next function. For example:

doesResolve('www.google.com')
        .then(result => result + " first function")
        .then(result => result + " second function")
        .then(result => logThis(result)); //logs "142.251.33.68 first function second function"

You can also return an additional Promise if the additional handlers need to wait.

Later, I'll be editing this post with the addition of Async/Await from ES7.