JavaScript 공부 [10. 콜백함수를 프로미스로 바꾸기]

JavaScript는 Synchronous하다.
그 말인 즉슨 hoisting 후 순차적으로 코드를 실행한다는 뜻이다.
Hoisting: var, function 선언

Callback Hell example

  1. id, password를 입력해서 loginUser 함수를 실행한다.
  2. 로그인이 되면 onSuccess를 통해 id를 받아온다.
  3. 받아온 id를 이용해서 getRoles 함수를 실행한다.
  4. 사용자 이름이 'minbro'이면 {name: 'minbro', roll: 'admin'} 이 출력된다.
"use strict";

class UserStorage {
    loginUser(id, password, onSuccess, onError) {
        setTimeout(() => {      // 로그인 하는데 2초정도 걸린다고 가정!
            if(
                (id === 'minbro' && password === 'minbro') ||
                (id === 'test' && password === 'test')
            ) {
                onSuccess(id)
            } else {
                onError(new Error('not found'))
            }
        }, 2000)
    }

    getRoles(user, onSuccess, onError) {
        setTimeout(() => {      // 권한 확인을 하는데 1초정도 걸린다고 가정!
            if (user === 'minbro') {
                onSuccess({name: 'minbro', roll: 'admin'})
            } else {
                onError(new Error('no access'))
            }
        }, 1000)
    }
}

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your passsword");
userStorage.loginUser(
  id,
  password,
  (user) => {
    userStorage.getRoles(
      user,
      (userWithRole) => {
          alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`)
      },
      (error) => {
        console.log(error);
      }
    );
  },
  (error) => {
    console.log(error);
  }
);


하지만 콜백함수 안에 콜백함수가 있어서 전달되는 파라미터가 어디로 전달되는지 헷갈려 가독성이 떨어지고, 에러가 어디에서 일어나는지 파악하는것도 어렵다.


Promise

Promise는 자바스크립트의 object로, 콜백함수 대신 사용할 수 있는 Asynchronous 기능이다.
State : pending -> fulfilled or rejected

Producer

const promise = new Promise((resolve, reject) => {
    // doing some heavy work(network, read files)
    console.log('doing something...')
})
// doing something...
// 바로 실행이 된다!

Promise는 Object를 만들면 executer가 바로 실행하기 때문에, 어떠한 이벤트로 Promise를 사용하고 싶은 경우에는 유의해야한다! (버튼을 누르면 동작한다던가...)

Consumer

then : 성공적으로 작동할 때!
catch : 에러 발생시!
finally : 성공/실패 여부와 상관없이 마지막에!

const promise = new Promise((resolve, reject) => {
  // doing some heavy work(network, read files)
  setTimeout(() => {
    resolve("minbro");
    // reject(new Error('no network'))
  }, 2000);
});

promise
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
      console.log('finally')
  })

Promise에서 성공하면 resolve 함수의 파라미터("minbro"), 실패하면 reject의 파라미터(new Error('no network'))를 리턴한다고 생각하면 쉽다!

Promise 예시

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('chicken'), 1000);
  });
const getEgg = hen =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${hen} => egg`), 1000);
  });
const cook = egg =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => sunny side up`), 1000);
  });

  getHen()
  .then(hen => getEgg(hen))
  .then(egg => cook(egg))
  .then(meal => console.log(meal))
// 3초 후
// chicken => egg => sunny side up
  
  getHen()
  .then(getEgg)
  .then(cook)
  .then(console.log)
// 인자가 하나만 있을경우 이렇게 생략해서 써도 된다고 한다!!
// 매우 놀랍다... console.log까지...?

Error Handling

위의 Promise 예시에서 에러 처리 기능을 추가해보자.

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("chicken"), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => egg`)), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => sunny side up`), 1000);
  });

getHen() //
  .then(getEgg)
  .catch(error => {
      return 'bread'
  })
  .then(cook)
  .then(console.log);
// 3초 후
// bread => sunny side up

.catch를 어디에 위치하느냐에 따라 에러처리가 달라지게 된다.

getHen() //
  .then(getEgg)
  .then(cook)
  .then(console.log)
  .catch(console.log);
// 3초 후
// Error: error! chicken => egg
// getEgg 에서 발생한 에러를 계속 넘겨주다가 마지막 catch에서 로그로 출력한다.

이제 에러처리를 배웠으니, 앞서 작성했던 Callback Hell Example 코드를 Promise를 사용하여 수정해보자

"use strict";

class UserStorage {
  loginUser(id, password) { // onSuccess, onError 파라미터를 쓰지 않는다!!!
    return new Promise((resolve, reject) => {	// 대신 Promise를 사용!!!
      setTimeout(() => {
        if (
          (id === "minbro" && password === "minbro") ||
          (id === "test" && password === "test")
        ) {
          resolve(id);				// onSuccess 대체!!
        } else {
          reject(new Error("not found"));	// onError 대체!!!
        }
      }, 2000);
    });
  }

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

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your passsword");
userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then((user) => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log);
// 코드가 훨씬 간결해지고 알아보기도 쉽다!

좋은 웹페이지 즐겨찾기