DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info on

JavaScript Clean Code: Concurrency

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

Even more articles at http://thewebdev.info/

Concurrency is an important part of most modern programs. To achieve this in JavaScript, we have to use asynchronous code, which is non-blocking.

In this article, we’ll look at how to write asynchronous code in a way that’s clean and easy to read and change.


Use Promises Instead of Callbacks

Promises have been a standard object since ES6, so the previous asynchronous callbacks should all be replaced with promises.

Using callbacks is a real pain if we have any sequential code since we have to nest them on multiple levels.

For example, if we want to run multiple setTimeout callbacks without promises, then we have to nest them as follows:

setTimeout(() => {
  console.log('foo');
  setTimeout(() => {
    console.log('bar');
    setTimeout(() => {
      console.log('baz');
    }, 200)
  }, 200)
}, 200)

As we can see, we only have three callbacks and the nesting is already very ugly. We have to clean this up so that this is more pleasant to look at and easier to understand.

We can do this with promises as follows:

const timeOutPromise = (str) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 200)
  })
}
timeOutPromise('foo')
  .then((val) => {
    console.log(val);
    return timeOutPromise('bar');
  })
  .then((val) => {
    console.log(val);
    return timeOutPromise('baz');
  })
  .then((val) => {
    console.log(val);
  })

As we can see, with promises, we can chain them with the then method with a callback passed into it. We don’t have to nest callbacks except in the timeoutPromise function, and it’s only two levels of nesting instead of three or more.

We get the resolves value of a promise in the parameter of the callback that we pass into the then method.

To catch errors, we can use the catch method with a callback passed in as follows:

const timeOutPromise = (str) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 200)
  })
}
timeOutPromise('foo')
  .then((val) => {
    console.log(val);
    return timeOutPromise('bar');
  })
  .then((val) => {
    console.log(val);
    return timeOutPromise('baz');
  })
  .then((val) => {
    console.log(val);
  })
  .catch((err) => console.error(err))


Async/Await Is a Cleaner Syntax for Chaining Promises

ES2017 introduced the async and await syntax, which is a cleaner way of chaining promises.

We can rewrite what we had above by writing:

const timeOutPromise = (str) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 200)
  })
}

(async () => {
  let val;
  val = await timeOutPromise('foo');
  console.log(val);
  val = await timeOutPromise('bar');
  console.log(val);
  val = await timeOutPromise('baz');
  console.log(val);
})();

It’s exactly the same as:

const timeOutPromise = (str) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 200)
  })
}

timeOutPromise('foo')
  .then((val) => {
    console.log(val);
    return timeOutPromise('bar');
  })
  .then((val) => {
    console.log(val);
    return timeOutPromise('baz');
  })
  .then((val) => {
    console.log(val);
  })

The one difference is that the resolved value is assigned to val via the assignment operator. This assignment works as long as we have await before our promises.

To handle rejected promises, we can use the try...catch clause as we do with synchronous code:

const timeOutPromise = (str) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 200)
  })
}

(async () => {
  try {
    let val;
    val = await timeOutPromise('foo');
    console.log(val);
    val = await timeOutPromise('bar');
    console.log(val);
    val = await timeOutPromise('baz');
    console.log(val);
  } catch (err) {
    console.error(err);
  }
})();

async functions only return promises, so we can’t use them as general-purpose functions. They’re syntactic sugar for promises and not a replacement of it.


Conclusion

To write asynchronous code, promises are the way to go. They let us chain multiple of them together without nesting callbacks.

We should convert asynchronous code to promises if they aren’t returned as promises already. To chain them, we can use the then method.

To catch errors from rejected promises, we can use the catch method.

async and await are syntactic sugar for promises. They’re the same thing but async and await is shorter.

We can use try...catch to catch errors from rejected promises with async and await.

The post JavaScript Clean Code: Concurrency appeared first on The Web Dev.

Oldest comments (0)