Async Programming with Callbacks, Promises, and Async/Await -Part 1-

Merve Bacak
Orion Innovation techClub
5 min readApr 28, 2022

Someone who wants to develop a program with JavaScript first learns how JavaScript works. When they learn basic concepts and start writing code, the information on how JavasScript works flies beneath the radar. So, I want to talk about event loop and async working before deep into dive callbacks, promises and async/await concepts that we use to do async programming.

Synchronous and Asynchronous Operations

When someone asks us what is Javascript, we can define it as a single-threaded, asynchronous programming language. Single-thread means it can do one thing one time, i.e. the code works step by step in a sequence.

Synchronous vs Asynchronous Operations

Looking at the image above, it can be a little bit confusing how single-thread and async operations work together. Synchronous operations are sequential tasks but in asynchronous operations, different tasks get in the thread. We can see the different tasks in different threads, but JavaScript’s thread has only one task at a time. So, JavaScript is single-threaded and also has async operations.

First of all, let’s understand why we need async operations. It is easier to predict how sync code works than async code, but it is disadvantage to write some tasks as sync operations once we realize that we need to wait for each step. For example, when we call ajax request, we wait for the response for this request. And we cannot execute any code blocks while waiting for the response. There is a gap between now and later where your program isn’t actively executing. This gap is a base for async programming. Because it can cause bugs and loss of performance and we should avoid them. If the program continues to work sequentially while it is waiting for a response, we prevent this loss of performance.

When JavaScript continues to execute the code sequentially while waiting for the response of a Browser API/Web API event or function, we can call it asynchronous operation. Because the task that we are waiting for is not actively executed from the JavaScript program, so we do not concurrent tasks on the single-threaded structure. Don’t let us get confused.

When we look at the code block above, we normally expect the program to write the log, wait 3 seconds and write another log on the console. However, because the setTimeout function is an async function, we see the output like this;

Async Function Example Output

The program writes the log into the console and runs setTimeout function in a different thread and continues to run while the function running. It writes the other log into the console and after 3 seconds it runs other function in setTimeout. Event Loop helps JavaScript for async works.

Event Loop

The event loop is one of the structures that ensure single-threaded JavaScript runs as multiple. We can think of this structure as an array, it runs forever. This array runs first-in first-out basis because it is like a queue. Elements in the array are an event because JavaScript plans how to work with events.

We can think of how the event loop works like the code block above. For instance, when any code block runs and makes a request, its callback function adds to the queue. After the request returns any response, the callback function waits for its turn in the queue. When it is the first function in the queue, works. We can guarantee that this callback function will not run without returning a response, but it can run late. Because the queue may be full.

Thus, thanks to the event loop, we ensure that other parts of our code continue to run actively while we wait for a response.

Callback Functions

Callbacks are the most common and basic way of expressing asynchrony in JavaScript. While describing asynchrony, we mentioned that there is a gap between the task that will run with the response and the currently running part of the program, and also, we can use this gap actively.

We can separate complex tasks into small parts with callbacks and related them to each other with time or sequence.

We define callbacks as parameters to a function. Thus, after sending our request, the relevant callback function is added to our event queue and our code continues to run. When the response is received, our callback function runs if the queue is empty.

In the example above, the parameter function of setTimeout function is a callback function. It is the part of the program, it runs after the specified delay. When the passing delay time, the program runs continue.

Functions can have multiple callback functions so that if the response to the request fails, the program can handle it.

In the example, we describe two callback functions and if is the value even, the success callback runs, if not, the failure callback runs.

function success(data) {
console.log(data);
}
function failure(err) {
console.log(err);
}
ajax(“url”, success, failure);

In this example, we describe two callback functions again, but in this case, we can handle and write the error on the console. But there are different bugs too.

  • The callback function in different requests can get the same value in the program and in this case, output changes according to response order. It’s called race-condition.

We normally expect to race-condition when we need to get and use some data with the HTTP request. But I want to show the example that you can run on the console easily.

When you run this code block and want to use x or y value in any operation, you can get two different results for each value in response order. And you can see the function names in a different order for each run.

  • The callback function may never be called.
  • The callback function may be called more than once.
let counter = 0;function printData(data) {    if(counter === 0){        console.log(data);        counter++;    }}ajax(“url”, printData);

For any request, we can check the callback function with the value in the main function before the run.

  • The callback function may be called too early.
function printData(data) {    if(data){        console.log(data);    }}ajax(“url”, printData);
  • The wrong parameter may be given.

We can check the parameter type and value of data and handle the bugs like this code block.

In case of need, we can define a new callback function inside a callback function, JavaScript allows it.

In the example above, code starts to run from the outermost function. And after the pass delay time, calls the callback function and this continues up to the innermost function.

Although this is possible, it is not preferred because it is difficult to understand. And it is called callback hell. We use promises to avoid this confusion when we need to write more than one callback function.

I tried to explain async programming and callbacks in this article. See you soon for the other part. Thanks for reading. :)

--

--