[JavaScript] Callback, Promise

37093 단어 JavaScriptJavaScript

Callback

JavaScript에서, 다른 함수의 매개변수로 전달되어 특정 이벤트가 발생한 후 다시 호출되는 함수를 Callback(Callback 함수)이라고 한다.

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');
  • 위 코드를 실행해보면, 순서대로 '1', '3', '2' 가 출력된다. 코드가 순서대로 호출되긴 하지만, setTimeout에 의해 1초가 기다려지는 구문은 브라우저에게 '1초 후 이 함수를 다시 돌려줘'라고 요청을 하고, 다음 코드를 실행하게 되는 것이다. 이 때, setTimeout()의 매개변수로 전달된 화살표 함수 () => console.log('2') 가 Callback 함수이다.

  • Callback은 꼭 비동기 방식에서만 사용되는 것은 아니다.

Synchronous Callback

function printImmediately(print) {
  print();
}
printImmediately(() => console.log('hello'));

Asynchronous Callback

function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}
printWithDelay(() => console.log('async callback'), 2000);

Callback 지옥

Callback을 함수 내부에 계속해서 사용하게 될 경우, 코드의 가독성이 떨어지고 유지보수가 어려워진다는 단점이 있다. 흔히 이러한 경우를 Callback 지옥이라고 부른다.

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'nine' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === 'nine') {
        onSuccess({ name: 'nine', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
  id,
  password,
  user => {
    userStorage.getRoles(
      user,
      userWithRole => {
        alert(
          `hello ${userWithRole.name}, you are ${userWithRole.role}!`
        );
      },
      error => {
        console.log(error);
      }
    );
  },
  error => {
    console.log(error);
  }
);
  • 위는 아주 간단한 코드이지만, Callback 함수가 계속해서 호출되므로써 가독성이 나빠지는 것을 확인할 수 있다.

Promise

위에서 살펴본 Callback의 단점을 보완하기 위한 방법으로, Promise가 있다. Promise에는 중요한 두 가지 요소가 있는데 State와 Producer/Consumer 이다.

  • State: Promise의 상태를 나타낸다.
    • pending: 대기 상태
    • fullfilled: 성공
    • rejected: 실패
  • Producer/Consumer
    • Producer: 데이터를 만드는 부분
    • Consumer: 데이터를 사용하는 부분

Promise의 사용

// 1. Producer
// when new Promise is created, the executor runs automatically.
const promise = new Promise((resolve, reject) => {
  // doing some heavy work (network, i/o files)
  console.log('doing something....');
  const id = prompt('insert id');
  setTimeout(() => {
    if (id === 'nine') {
      resolve('nine');
    } else {
      reject(new Error('no network'));
    }
  }, 2000);
});

// 2. Consumers
// use 'then, catch, finally' for get result of resolve
// value는 promise가 정상적으로 실행되어서 resolve로 전달된 값
// 성공했다면 resolve, 실패하면 reject의 값을 전달받고
// 성공 시 then으로, 실패 시 catch로 값을 받는다.
// finally는 성공 실패 여부와 관계없이 무조건 한 번 실행된다.
promise
  .finally(() => {
    console.log('this is finally()');
  })
  .then(value => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  });

// 3. Promise chaining
const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then(number => number * 2)
  .then(number => number * 3)
  .then(number => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(number - 1), 1000);
    });
  })
  .then(number => console.log(number));

// 4. Error Handling
const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐔'), 1000);
  });

const getEgg = hen =>
  new Promise((resolve, reject) => {
    // setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`), 1000));
  });

const cook = egg =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen()
  //   .then(hen => getEgg(hen))
  // 한가지만 받아서 전달하는 경우에는, 생략이 가능하다.
  // 오류가 발생한 곳에 catch를 적절히 넣어, 다른 값으로 대체할 수 있다.
  .then(getEgg)
  .catch(error => {
    return '🥖';
  })
  .then(cook)
  .then(console.log);

Callback 예제를 Promise로 변환

위에서 살펴본 Callback의 예제를 Promise로 변환하면 아래와 같이 간단하게 바뀐 것을 볼 수 있다.

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === 'nine' && password === 'dream') ||
          (id === 'coder' && password === 'academy')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

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

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage
  .loginUser(id, password)
  .then(id => userStorage.getRoles(id))
  .then(user => alert(`hello ${user.name}, you are ${user.role}!`))
  .catch(console.log);

좋은 웹페이지 즐겨찾기