JavaScript Interview Questions: Promises

John Au-Yeung - Jan 24 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

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

To get a job as a front end developer, we need to nail the coding interview.

Promises are important parts of JavaScript because of the pervasive use of asynchronous programming.

In this article, we’ll look at some common questions about JavaScript promises.

What are Promises?

Promises are one way to handle asynchronous operations in JavaScript. Each promise represents a single operation.

It’s made to solve the problem of combining multiple pieces of asynchronous code in a clean way.

With promises, we can avoid nesting callbacks too deeply.

For example, instead of writing deeply nested callbacks as follows:

const fs = require('fs');

fs.readFile('somefile.txt', function (e, data) {

  fs.readdir('directory', function (e, files) {

    fs.mkdir('directory', function (e) {

    })
  })
})
Enter fullscreen mode Exit fullscreen mode

We write:

const fs = require('fs');

const promiseReadFile = (fileName) => {
  return new Promise((resolve) => {
    fs.readFile('somefile.txt', function(e, data) {
      resolve(data);
    })
  })
}

const promiseReadDir = (directory) => {
  return new Promise((resolve) => {
    fs.readdir(directory, function(e, data) {
      resolve(data);
    })
  })
}

const promiseMakwDir = (directory) => {
  return new Promise((resolve) => {
    fs.mkdir(directory, function(e, data) {
      resolve(data);
    })
  })
}

promiseReadFile('somefile.txt')
.then((data)=>{
  console.log(data);
  return promiseReadDir('directory')
})
.then((data)=>{
  console.log(data);
  return promiseMakwDir('directory')
})
.then((data)=>{
  console.log(data);
})
Enter fullscreen mode Exit fullscreen mode

We chained promises instead of nesting them as follows:

promiseReadFile('somefile.txt')
.then((data)=>{
  console.log(data);
  return promiseReadDir('directory')
})
.then((data)=>{
  console.log(data);
  return promiseMakwDir('directory')
})
.then((data)=>{
  console.log(data);
})
Enter fullscreen mode Exit fullscreen mode

This is why we want to write promises for long chains of promises instead of deeply nested callbacks. It’s much easier to read.

Promises have 3 states. They can be pending, which means the outcome isn’t known yet since the operation hasn’t start yet.

It can be fulfilled, which means that the asynchronous operation is successful and we have the result.

It can also be rejected, which means that it failed and has a reason for why it failed.

A promise can be settled, which means that it’s either fulfilled or rejected.

Also, in the code above, we called the resolve function to return the value of the promise. To get the resolved value of the promise, we get the resolved value from the parameter of the callback.

data in each callback has the data that’s resolved from the promise above it.

To run the next promise in the callback, we returned the next promise.

To reject a promise, we call the reject function in the callback that we pass into the Promise constructor.

For example, we can change the code above to the following:

const fs = require('fs');

const promiseReadFile = (fileName) => {
  return new Promise((resolve, reject) => {
    fs.readFile('somefile.txt', function(e, data) {
      if (e){
        reject(e);
      }
      resolve(data);
    })
  })
}

const promiseReadDir = (directory) => {
  return new Promise((resolve, reject) => {
    fs.readdir(directory, function(e, data) {
      if (e){
        reject(e);
      }
      resolve(data);
    })
  })
}

const promiseMakwDir = (directory) => {
  return new Promise((resolve, reject) => {
    fs.mkdir(directory, function(e, data) {
      if (e){
        reject(e);
      }
      resolve(data);
    })
  })
}

promiseReadFile('somefile.txt')
.then((data)=>{
  console.log(data);
  return promiseReadDir('directory')
})
.then((data)=>{
  console.log(data);
  return promiseMakwDir('directory')
})
.then((data)=>{
  console.log(data);
})
.catch((err)=>{
  console.log(err);
})
Enter fullscreen mode Exit fullscreen mode

Once a promise rejected, the ones that come after it won’t run.

We can get the error details and do something with it in the catch function’s callback like we have above.

In the code above, we converted each asynchronous code into a promise by creating a function and then returning a promise in it that calls resolve to fulfill the promise and reject to reject the promise with an error value.

What is async/await?

async/await is a new way to write promise chains in a shorter way.

For example, instead of calling the then function with a callback like we had above, we rewrite it to:

try {
    const readFileData = await promiseReadFile('somefile.txt');
    console.log(readFileData);
    const readDirData = await promiseReadDir('directory');
    console.log(readDirData);
    const makeFileData = await promiseMakwDir('directory');
    console.log(makeFileData);
    return makeFileData;
  }
  catch(err){
    console.log(err);
  }
})();
Enter fullscreen mode Exit fullscreen mode

This is the same as:

promiseReadFile('somefile.txt')
.then((data)=>{
  console.log(data);
  return promiseReadDir('directory')
})
.then((data)=>{
  console.log(data);
  return promiseMakwDir('directory')
})
.then((data)=>{
  console.log(data);
})
.catch((err)=>{
  console.log(err);
})
Enter fullscreen mode Exit fullscreen mode

As we can see, async/await is much shorter than writing then with callbacks.

We don’t have callbacks and we don’t have to return promises in each callback.

readFileData, readDirData, and makeFileData are the same as the data parameter in each then callback.

try...catch in the async function is the same as the catch call and callback at the end. err has the same error object in both examples.

async functions can only return promises, so makeFileData is actually the resolved value of a promise, not the actual value.

Even though it looks like a synchronous function, it doesn’t behave the same.

Conclusion

Promises are a way to chain asynchronous operations cleanly without deeply nested callbacks.

We can make them even shorter and cleaner with the async/await syntax.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .