DEV Community

loading...
Cover image for All you need to know about Promises for your next coding interview

All you need to know about Promises for your next coding interview

albert_hadacek profile image Albert Hadacek Updated on ・5 min read

JavaScript is a single-threaded language, which means it can only do one thing at a time. Most of our standard code is synchronous therefore the JavaScript interpreter runs it line by line. Yet, in certain situations we need to wait for operations, that take some time e.g. reaching a database or some API.

We could pause the code, wait for our response and then continue, yet that sounds quite inefficient. Just imagine, you are on Twitter and you want more tweets to be loaded on your feed. If the code stopped while fetching them, you would not be able to interact with other features of the app. For that reason, asynchronous mechanisms were introduced in the language.

This article will help you understand the core concepts of asynchronous code, you will master promises while learning all the abstract concepts that might appear in your coding interview. Let’s dive in.

Call Stack and Callback Queue

Before we start talking about promises, we need to understand two core features of JavaScript — call stack and callback queue.
The stack is a data structure that works on the LIFO principle (last in, first out). Your synchronous function calls are always pushed to the call stack and when they finish they are popped.

const multiply = (a, b) => a + b

const square = (num) => multiply(num, num)

square(4) // 16
Enter fullscreen mode Exit fullscreen mode

In the example above we first push the square function that is being invoked on the call stack, inside of that function we call multiply, so that is also pushed to the stack. When the multiply returns, it’s popped, and then when square returns the returned value of multiply, it is also popped. So, now the stack is empty.

Things get a bit more complicated when we use the setTimout function. This function is actually not part of the JS itself, but it is a function to control the Timer offered by the browser API (similarly to local storage, cookies, etc…).

const hi = () => console.log("hi")

setTimeout(hi,0)

console.log("First or second?")

// CONSOLE:
// "First or second"
// "Hi"
Enter fullscreen mode Exit fullscreen mode

Why is that happening? Well, the setTimout triggers the Timer in the browser, which spends 0 milliseconds waiting, and then (this is the important part) it does not go back to the call stack but to the callback queue (FIFO). There it waits until all the synchronous code is done (in our case just the logging) and after that, it is pushed to the call stack.

If we have several asynchronous actions happening they would be queued. This process is controlled by the so-called event loop, which is checking if the stack is finally free (all synchronous code is done).

Introduction to promises

Conceptually promises in JavaScript work the same way as in real life. Someone promises to help you, but he can break the promise.

In the world of JavaScript, promises are represented as objects that can have three states — pending, fulfilled, or rejected.

const p = new Promise((resolve, reject) => {
    const promiseKept = true
    if(promiseKept) {
        resolve("Promise was kept")
    } else {
        reject("Promised was not kept")
    }
})

p.then(message => {
    console.log(message);
})
.catch(message => {
    console.log(message)
})

// CONSOLE:
// "Promise was kept"
Enter fullscreen mode Exit fullscreen mode

The above example is a simple promise using synchronous code. If we change the promiseKept variable to false, the promise will be rejected and the code inside the catch() method will be triggered.

Formally, promises are special objects built into JavaScript, that can trigger outside the world actions (XHR requests, etc…), but they are immediately returned, so we can work with them. We can attach additional functionality to them which will run when the background work is done.

We predominantly work with built-in functions that return promises such as fetch, which triggers the browser’s XHR feature.

const data = fetch("someApi/posts")

data.then(posts => console.log(posts))

console.log("First or second?")

// CONSOLE
//  "First or second?"
// ["Post 1", "Post 2"]
Enter fullscreen mode Exit fullscreen mode

In the snipped above, we create a data variable that is assigned to a promise object. Then we add the callback function to the hidden property onFulfillment (array) using the then() method. When the XHR requested is completed, the value property on our data variable is updated and the event loop takes care of our callback.

The previous example works similarly to the snippet with setTimout() with one exception. The Promise never ended up on the callback queue.

setTimeout(() => console.log("Hi", 0)

const data = fetch("someApi/posts")

data.then(posts => console.log(posts))

console.log("First or second or third?")

// CONSOLE
// "First or second or third?"
// ["Post 1", Post 2"]
// "Hi"
Enter fullscreen mode Exit fullscreen mode

Confusing? Well, it is. JavaScript actually has one more queue called microtask queue or job queue, where Promises are "stored" as they take precedence over Timer. To clarify, items from the microtask queue are pushed to the call stack before the ones from the callback queue. So, even though our XHR request might have taken 300ms it will appear on the call stack before the timer. To know what goes where you need to check the official specification of the language.

Also, try to visualize the concepts using this great tool (www.latentflip.com/loupe).

Chaining promises

One extremely cool feature of promises is chaining. We are not limited to have only one callback function when our promise is fulfilled. We can have several and they always receive the returned value from the previous one as their argument. We can use it for instance for loading scripts one by one.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve(10);
  }, 1000);
});

p.then((result) => {
  console.log(result); // 10
  return result * 2;
}).then((newResult) => {
  console.log(result); // 20
});
Enter fullscreen mode Exit fullscreen mode

Error handling

Performing XHR requests and other asynchronous actions might be unpredictable. What if the API we are trying to reach is currently out of service. Well, that's the prime case for using catch(). The callback function used to invoke the catch method will be stored in the onRejection array and triggered if the promise is unfulfilled.

const data = fetch('api-not-working/posts') 

data.then(response => response.json())
    .catch(err => alert(err))
Enter fullscreen mode Exit fullscreen mode

Async/Await

In ES7 a new set of keywords was introduced - async and await. I won't dive deep into them as I am planning an article on generators and iterators, where I am gonna discuss async/await in much more detail. Yet, it is handy to know the syntax as it's replacing the more traditional way of handling promises in modern codebases.

const fetchData = async () {
    try {
        const data = await fetch("someApi/posts")
        console.log(data)
    } catch(e) {
        alert(e)
    }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (3)

pic
Editor guide
Collapse
darkwiiplayer profile image
DarkWiiPlayer

The stack is a data structure that works on the FIFO principle (first in, first out).

A stack is, by definition, a LIFO structure. A "stack" that's FIFO is actually just a queue. That's the main distinguishing factor between those two data structures.

Collapse
alimobasheri profile image
Mir Ali Mobasheri

Thanks for the article. Well described. I'll be waiting to read your article on generators. 😄

Collapse
siddharthgaglani profile image
siddharth-gaglani

Great explanation. Thanks!