Stanley Sathler

Synchronous and asynchronous in computer systems, and their usage in JavaScript through Promises

Table of contents

tl;dr

  • Synchronism is often referred as “sync”; asynchronism, as “async”.
  • The sync blocks a main control flow during a heavy operation.
  • The sync makes elements idle for a period of time.
  • The async doesn’t having idle elements.
  • The async doesn’t block the main control flow. The heavy operation is performed in a secondary flow control.
  • In async, when the heavy operation is done, the main flow receives a message from the secondary flow control telling it is done.
  • Sync / asyn can be used in different situations. OS’ process managers use asynchronism for I/O. Client-Server apps can use asynchronism for network requests.
  • Sync / async are both concepts. Promises are the concrete implementation of async in JavaScript.

Introduction

If you are a programmer, you’ve probably heard about shynchronous and asynchronous operations. Being personally a JavaScript programmer, the first time I heard about them was through Promises.

Promises will be briefly explained later in this post.

At that time, I understood how to use Promises, but not how to create them. Worse: I didn’t understand when to create them. That happened because I didn’t understand what sync (synchronism) and async (asynchronism) were.

Both terms refer to techniques used by far more than just high-level programming languages.

It can be used by kernels. It can be used by user applications. It can be used by network protocols. It can be used in different situations.

Sync and async are concepts. They are solutions to a specific problem. It can be implemented in different ways for different scenarios.

A funny analogy

Let’s say you’re a messenger. You deliver letters from people to people living in different cities. But you are special. You don’t just deliver: you wait for the response, so you can bring it back. And keep doing it back and forth.

Each person takes 3 days to write their letter. It means that once delivered, you need to stay around (sleeping in an inn) for 3 days. You basically stay idle for 3 days.

As the number of people grows, your waste increases. 3 days being idle are 3 days that you could be making some money.

You’re sync. You’re synchronized. When someone takes 3 days to give you something, you stay idle, without doing anything.

However, you realized that you could do better. Why waiting for 3 days without doing anything? What if, within those 3 days, you went to the next city and delivered one more letter to one more client?

Then, when the first client finishes it (after 3 days), you just go back there to collect his response letter?

This way you wouldn’t be idle. While someone is writing his letter, you can deliver the other ones. Once a client finishes writing the letter, he just tells you, and you go back there to collect it.

Now you’re being async. You’re not getting idle when something else is happening; instead, you’re performing better by delivering the other letters in the meantime.

Sync operations

Sync operations are executed sequentially. They’re sequential, synchronized. A second instruction only executes after the first one has finished.

That’s the concept of sync operations: a second operation only really executes once the first one is fully finished. It creates some sort of dependency.

That’s the case of multiple algorithms you write every day. In most cases, I believe that you write programming instructions that are executed sequentially.

The problem behind sync operations

The biggest problem of sync operations is: if your first operation is heavy, your process becomes blocked until the first operation is done and your second one can be started.

This blocked state makes your components idle.

In a word where we expect to have things running as fast as possible, being idle is a luxury that we can’t have.

Idle elements are the biggest problem of sync operations.

Anything that matches this criteria can be considered sync.

If your browser stops building the DOM Tree when it finds a JavaScript code in your HTML, you have a sync operation.

If your CPU stops executing new instructions when an I/O operation is started, you have a sync operation.

If you have any blocked state while doing a heavy thing, you have a sync operation.

Many things out there are sync, mostly because they make sense being sync.

A flowchart showing a sync flow

Async operations

Having idle components is a wasteful. The modern computing era needs to be as fast as possible, and having idle things goes against this goal.

Async is when you let a heavy operation to be done in a separate flow control and, in the meantime, do something else in the main flow control.

That’s it. That’s the concept behind async operations.

Async operations have these two following characteristics:

  • It allows you to run something in a secondary flow control and run something else in the main one;
  • It tells you when the heavy operation is done, so you can act upon its result.

In programming languages, async can be implemented using concurrency models like multithreading or Event Loop.

Async in operating system’s process managers

Systems that are multiprogrammed have a process manager running a scheduling algorithm to make the maggic happen.

Basically, each process uses the CPU at a time, and processes have statuses.

When a process starts an I/O operation, it changes its status to waiting. The I/O can take some time - it’s unpredictable.

In the meantime, the CPU is not idle without running an instruction - the process manager puts another process in the CPU. This way the CPU can stay busy all the time.

Once the I/O operation is finished, the kernel sends a message to the process manager. It then sets the original process’ status from waiting to ready, so that it can be re-executed as soon as possible.

The process manager uses a concrete implementation of the async concept.

Async in Client-Server applications

In the Client-Server architecture you have machines (clients) making requests to other machines (servers). In this type of architecture, the client always awaits a response.

There is a gap between the request and the response. It’s commonly fast, but it’s unpredictable as well. It can take 10 seconds.

Since the network request itself is not handled by your program (routers, operating systems and other devices often do this in their kernel themselves), you have an idle time while the request is sent to another machine.

Why waiting for that to execute the next line? Why letting our program to be idle? We can execute some other lines of code while the request is in progress.

A flowchart showing an async flow

Promises: async in JavaScript

You’ve learned that async can be used in different situations. It’s a concept. It’s not a JavaScript feature, nor a feature from any language at all.

However, programming languages offer support for async because programs also suffer from the problems that come from sync operations.

In JavaScript, async is implemented through Promises.

Promises is the name given to the class of objects designed to support async in JavaScript. Async is not a JavaScript feature, but Promises are. Async is a concept, but Promises are a concrete implementation of async in JavaScript.

As many other classes, it contains methods and an inner state. Also, variables that point to a Promise object are of Promise type.

States of a Promise

A Promise is always in either one of these states:

  • Pending: the heavy operation is not completed yet. It’s the initial state.
  • Fulfilled: the heavy operation has completed successfully.
  • Rejected: the heavy operation had an error.

Acting upon the result

As you’ve learned before, an asynchronous operation sends you a message when the heavy operation is finished, so you can act upon it.

In JavaScript, you pass a callback to your Promise. A callback is a regular function. When the heavy operation is done, the JavaScript calls your callback - the function that acts upon the result.

To do so, you can use both .then() and .catch() methods.

myPromise
  .then(() => console.log('success'))
  .catch(() => console.log('error'))

Creating a Promise

You’ll often see yourself using others’ Promises, but at some point at your career, you’ll have to create your own for a specific problem.

Sounds weird at the first time. Relax. If you don’t understand it now, you’ll surely do when you have to really implement your own Promise.

var myPromise = new Promise((resolve, reject) => {
  // Both `resolve` and `reject` are functions.
  // You can call them anytime. Probably when your
  // heavy operation is done.

  var heavyResult = doHeavyOperation();

  if (heavyResult.success)
    resolve(heavyResult)
  else
    reject(heavyResult)
})

myPromise
  .then(result => console.log('success'))
  .catch(result => console.log('error'))

Promises in JavaScript have way more features and details. You can check the the awesome post from MDN, where they explain Promises in details.

Conclusion

Now you’ve learned the basic principles behind the (a)async concepts, you’re way more prepared to apply them in your routine.

Now you’ve learned that (a)sync are abstract concepts and the basic principles behind them, you can now use the concrete implementations depending on your context.

Dealing with async programming while using JavaScript? You have Promises to help you achieve that.

Written by Stanley Sathler, a passionate Software Engineer and a forever Computer Science student.