frontend Oct 15

Asynchronous Javascript

5 min read –
locastic web and mobile apps

Now that I got your attention, I’ll start with saying that Javascript and asynchrony have nothing in common!

At its core Javascript is synchronous, blocking, single-threaded language, which means that only one thing can be in progress at the time. Single-threaded language makes writing code easier because you don’t have to deal with concurrency issues, but on the other hand it also prevents you from performing long operations such as network requests without blocking the main thread.

Let’s say you are trying to fetch some data from an API. It can take few seconds until server process the request and send back the data, while blocking the main thread and making everything else unresponsive.

This is just one example where and why people needed asynchronous operations in Javascript.

In this article we will go trough several ways of doing them:

  • callbacks
  • Promise
  • async – await

Callbacks

First of all, name ‘callback’ isn’t official Javascript name for asynchronous functions. In the matter of fact, it’s just a naming convention for using asynchronous JS functions . Basically, callbacks are functions that are being executed after another function has finished its own executing. Any function that is passed as an argument is a callback function.

Basic callback example would be using addEventListener function:

function callback(){
	alert('Clicked')
}

addEventListener('click',callback)

This was the first attempt to do async work in Javascript, and it was even working for a while, but with more complicated solutions came more complicated callbacks which led to something like this:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

Just look at this bad boy! There is a reason why people refer to it as callback hell. We have callback inside callback in a callback – it’s an inception of callbacks. That’s the reason why Javascript developers came up with something new, easier to understand.

Promises

Promises are designed to be more readable so we could use and understand them easier. Basically, Promise is an object that wraps an asynchronous operation and notifies when it’s done. Promise has it’s own method using which you can tell the Promise what you want to happen when the Promise is successful or when it fails. that method is .then, or .catch for when something went wrong.

doSomething()
	.then(res=>doSomethingElse(res))
	.then(res=>doSomethingOther(res))
	.catch(err=>doSomethingWithError(err))

doSomething function is not a Promise by itself but a function that returns a Promise.

Now it’s easy to see what problems Promises are trying to solve. Instead of nesting callbacks in the callbacks and so on, we simply chain .then() calls together making the code more readable, understandable and better.

Important thing to mention is that any error in the promise chain will cause chain to stop execution and go to the .catch() block.

We could also create our own Promise:

const something = true
promise = new Promise(function(resolve,reject){
	if(something){
		resolve('Something is true');
	}else{
	  reject('Something is false')
	}
})

The new Promise constructor accepts a function which accepts two parameters resolve and reject which are functions by themselves. If the asynchronous operation is successful then the expected result is return by calling resolve function. If operation is not successful or any errors occurred while executing operation reject is called and reason is passed on.

Promises are great, but there is special syntax which allows us to work with them in a more comfortable fashion.

Async/await

Newest addition to asynchronous Javascript is async-await functions. Async and Await are extensions of Promises or more elegant way to deal with them. Async keyword allows us to write asynchronous code without blocking the main thread, as it was synchronous. If we put async in front of the function, it implies that the function will return a value inside the Promise. To be able to access that value we would need to use .then( ) or use await.

async function someFunction() {
	return 'this is the async function'
}

someFunction().then(alert) // this is the async function

The await is used to wait for the Promise to return the result and it can only be used inside async block.

async function someFunction() {
	let promise = new Promise((resolve,reject)=>{
		setTimeout(()=>resolve('this is the async function'),2000)
		});
	const result = await promise;
	alert(result)  // this is the async function
}

Result of this example will be the same as the example above but this time await is used to wait for the Promise to return something and then alert it.

So you may be wondering why bother with async-await syntax if you are already familiar with the Promises? There are few points I’d like to present to answer that question.

Error handling

Let’s imagine we have the following situation: we want to get some data from the server, then output it on our website.

The code using Promises would look something like this:

function getData() {
  try {
    fetchData()
      .then(result => {
        const data = JSON.parse(result)
        console.log(data)
      })
  } catch (error) {
    console.log(error)
  }

Problem with it is in JSON.parse function which may fail, but we wouldn’t know that because the catch block can’t catch that error because it happened inside Promise. One way to solve this would be to use another .catch for the Promise and duplicate error handling code.

Better way to solve this is just to use async/await:

async function getData() {
  try {
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (error) {
    console.log(error)
  }
}

Now, the .catch will handle any error and we don’t have to duplicate the code.

Intermediate values

Let’s say we have a situation with two Promises and we want to use result from first Promise to call second Promise.

function getData() {
  return firstPromise()
    .then(result1 => {
      return secondPromise(result1)
    })
}

Same example but using async/await:

async function getData() {
  const result1 = await firstPromise()
  return secondPromise(result1)
}

At the first glance, not much has changed and we didn’t profit much on the amount of written code. But what if we require more Promises, with each one depends on the result of the one before?

function getData() {
  return firstPromise()
    .then(result1 => {
      return secondPromise(result1)
	.then(result2=> {
	  return thirdPromise(result1, result2)
	})
    })
}

It gets messy really quickly, which is not the case with async/await. No matter how many Promises we add it’s quite readable and easy to understand.

async function getData() {
  const result1 = await firstPromise()
	const result2 = await secondPromise(result1)
  return thirdPromise(result1,result2)
}

Conditionals

Sometimes sending several requests to get the desired data is required. For example, we need to fetch our users data and based on conditions we can include user location.

function getUserData() {
  return fetchUser()
    .then(data => {
      if (data.needsLocation) {
        return fetchUserAgain(data)
          .then(dataWithLocation => {
            return dataWithLocation
          })
      } else {
        return data
      }
    })
}

As you can see, it’s quite unreadable and I easily get lost in all that nesting and return statements. Let’s rewrite it using async/await:

async function getUserData() {
	const data = await fetchUser();
	if(data.needsLocation){
	   const dataWithLocation = await fetchUserAgain(data);
	   return dataWithLocation;
	}  
	else{
	   return data
	}
}

It is way more readable and concise.

Clean syntax

As you can see in the examples above, we saved decent amount of code by implementing async/await syntax and the syntax itself is generally very clean and easy to read. We also didn’t have to nest our code much, write .then or declare variables we didn’t need to use.

Wrap up

Even though we used three different methods to do some asynchronous work, at the end of the day, all of them are doing the same work. The async/await syntax may look synchronous but it executes just like callback or Promise API. It just provides more concise way to write asynchronous code in Javascript.

If you’d like to know a bit more on the matter, make sure you take a deeper look into JS Generators here.