본문 바로가기
Web/JavaScript

[JavaScript] Callback, Promise, Async/Await

by llHoYall 2020. 9. 18.

In the case of synchronous code, the code runs in blocking mode. In other words, the next statement can not be executed and waits until the previous statement is completed.

 

So, we need to use the asynchronous code.

If we use the asynchronous code, we can execute the next statement simultaneously and execute statements related to asynchronous code after the asynchronous code finishes.

 

Now, Let's find out the asynchronous code.

Async Callbacks

class UserData {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === "AAA" && password === "1111") ||
        (id === "BBB" && password === "2222")
      ) {
        onSuccess(id);
      } else {
        onError(new Error("Not Found"));
      }
    }, 1000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === "AAA") {
        onSuccess({ name: "AAA", role: "admin" });
      } else {
        onError(new Error("No Access"));
      }
    }, 1000);
  }
}

const userData = new UserData();
const id = prompt("Enter you ID: ");
const password = prompt("Enter you password: ");
userData.loginUser(
  id,
  password,
  user => {
    userData.getRoles(
      user, 
      userWithRole => {
        console.log(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
      },
      error => {
        console.log(error);
      }
    )
  },
  error => {
    console.log(error);
  }
);

If you run this code, you need to input the id and password.

Please try it until you fully understand.

 

There are two valid inputs.

  • id: "AAA", password: "1111"
  • id: "BBB", password: "2222"

If the input is not valid, the onError function of the loginUser method will be executed.

If the input is valid, the onSuccess function of the loginUser method will be executed.

I gave the getRoles method as onSuccess function.

 

Now, let's assume the valid input you gave.

After one second, in other words, the login request is successful, the getRoles method will be executed.

 

If the input id is "BBB", the onError function of the getRoles method will be executed.

If the input id is "AAA", the onSuccess function of the getRoles method will be executed.

I gave the nonamed log function as the onSuccess function, and the object as the argument.

{
  name: "AAA",
  role: "admin"
}

So, the log will be printed with this object.

 

However, as you can see, if asynchronous operation is nested, you will fall into callback hell.

In addition, this style is hard to handle errors in handling asynchronous.

Promises

const practice = new Promise((resolve, reject) => {
  console.log('Running practice...');
  setTimeout(() => {
    resolve('RESOLVE');
    reject(new Error('REJECT'));
  }, 1000);
});

practice
  .then(value => console.log(value))
  .catch(error => console.log(error))
  .finally(() => console.log('FINALLY'));
  
//=> Success case  
// Running practice...
// RESOLVE
// FINALLY

//=> Failure Case
// Running practice...
// Error: REJECT
// FINALLY

This is a simple example of the Promise for usage and error handling.

If the asynchronous operation successes, the resolve function will be executed, and if not, the reject function will be executed.

If you want to test the failure case, comment out the resolve('RESOLVE') line.

The finally function will be always executed.

As you can see, this style can handle errors easier than callback styles.

 

Now, let's see the nested case.

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("HEN"), 1000);
  });
  
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(
      () => resolve(`${hen} => EGG`),
      1000
    );
  });
  
const getCook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => COOK`), 1000);
  });

getHen()
  .then(getEgg)
  .then(getCook)
  .then((cook) => console.log(`${cook}`));
// HEN => EGG => COOK

It can be simply nested.

getEgg function is as resolve function of the getHen function, and getCook function is as resolve function of the getEgg function.

 

Then, let's change the previous example with Promise.

class UserData {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "AAA" && password === "1111") ||
          (id === "BBB" && password === "2222")
        ) {
          resolve(id);
        } else {
          reject(new Error("Not Found"));
        }
      }, 1000);
    });
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "AAA") {
          resolve({ name: "AAA", role: "admin" });
        } else {
          reject(new Error("No Access"));
        }
      }, 1000);
    });
  }
}

const userData = new UserData();
const id = prompt("Enter you ID: ");
const password = prompt("Enter you password: ");
userData
  .loginUser(id, password)
  .then(userData.getRoles)
  .then(user => console.log(`Hello ${user.name}, you have a ${user.role} role`));

I modified the above example with the Promise, so the operation is the same as the above example

The code became more simple and readable.

Async/Await

The last style is async/await. It is the most recent thing.

This style improves readability and allows you to write asynchronous code as if it were a synchronous code.

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function getApple() {
  await delay(1000);  
  return "apple";
}

async function getBanana() {
  await delay(2000);
  return "banana";
}

This is a simple example.

Attach the await keyword to the asynchronous code and async keyword to the function that contains the asynchronous code.

async function pickFruits() {    
  const apple = await getApple();
  const banana = await getBanana();
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log);
// apple + banana

The pickFruits function waits for all the asynchronous code and executes then method.

function pickOnlyOne() {
  return Promise.race([getApple(), getBanana()]);
}

pickOnlyOne().then(console.log);
// apple

The race method of Promise makes to race for asynchronous codes and gets only the fastest one.

function pickAllFruits() {
  return Promise.all([getApple(), getBanana()])
    .then((fruits) => fruits.join(" + "));
}

pickAllFruits().then(console.log);
// apple + banana

The all method of Promise waits for the slowest code and gets all.

'Web > JavaScript' 카테고리의 다른 글

[TypeScript] Dev. Environment Configuration with VS Code  (0) 2020.10.12
[Jest] JavaScript Code Unit Testing  (0) 2020.10.01
[JavaScript] JSON (JavaScript Object Notation)  (2) 2020.09.17
[React] useState Hooks  (0) 2020.09.02
[React] prop-types  (0) 2020.09.02

댓글