[TIL-20210623] Callback / Promise / async await

Pizzahand·2021년 6월 27일
0

TIL

목록 보기
17/28

콜백(Callback)

비유로 이해하는 콜백함수

콜백 함수의 동작 방식은 유명 맛집 자리 예약과 같다. 유명한 맛집을 가면 자리가 없을때가 있어서 대기자 명단에 이름과 연락처를 쓰고 자리가 날 때까지 주변을 돌아다니죠. 만약 식당에 자리가 생기면 전화로 자리가 났다고 연락이 온다. 그 전화를 받는 시점이 콜백 함수가 호출되는 시점과 같다. 손님 입장에서는 자리가 날 때까지 식당에서 기다리지 않고 근처 가게에서 잠깐 쇼핑을 할 수도 있고 아니면 다른 식당 자리를 알아볼 수도 있다. 자리가 났을 때만 연락이 오기 때문에 미리 가서 기다릴 필요도 없고, 직접 식당 안에 들어가서 자리가 비어 있는지 확인할 필요도 없습니다. 자리가 준비된 시점, 즉 데이터가 준비된 시점에서만 우리가 원하는 동작(자리에 앉는다 / 특정 값을 출력한다 등)을 수행할 수 있다.

콜백 에러 핸들링(Error handling)

const getDataFromFile = function (filePath, callback) {
  fs.readFile(filePath, "utf8", function (err, file) {
    if (err) {
      callback(err, null);
    } else {
      callback(null, file.toString());
    }
  });
};

위 코드는 fs.readFile을 통해 파일을 읽어올 때, 파일을 읽는데 실패할 경우(error) callback(err,null)을 호출하고, 성공했을 경우 callback(null, file.toString())를 호출한다. 위와 같은 패턴으로 에러를 처리하는 방식을 오류 우선 콜백(error-first callback)이라고 한다.

오류 우선 콜백은 다음 관례를 따른다.

  • callback의 첫 번째 인수는 에러를 위해 남겨둡니다. 에러가 발생하면 이 인수를 이용해 callback(err)이 호출됩니다.
  • 두 번째 인수(필요하면 인수를 더 추가할 수 있음)는 에러가 발생하지 않았을 때를 위해 남겨둡니다. 원하는 동작이 성공한 경우엔 callback(null, result1, result2...)이 호출됩니다.

콜백 지옥

콜백 기반 비동기 처리는 언뜻 봤을 때 꽤 쓸만해 보이고, 실제로도 그렇지만 하지만 꼬리에 꼬리를 무는 비동기 동작이 많아지면 아래 그림과 같은 코드 작성이 불가피해진다.

[그림] 콜백 지옥의 예위 그림과 같이 콜백에 콜백에 콜백....으로 이루어진 코드처럼 깊은 중첩 코드가 만들어내는 패턴은 소위 ‘콜백 지옥(callback hell)’ 혹은 '멸망의 피라미드(pyramid of doom)'라고 불린다.
이러한 콜백 지옥을 벗어나기 위해서는 Promise나 async await을 이용하여 코드를 작성하는 것이 좋다.

프로미스(Promise)

프로미스의 3가지 상태(states)

여기서 말하는 상태란 promise의 처리 과정을 의미한다. new Promise()로 promise를 생성하고 종료될 때까지 아래와 같은 3가지 상태를 갖는다.

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 promise가 결과 값을 반환해준 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
new Promise(); //new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.

new Promise(function(resolve, reject) {
  // ...
}); //new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject 이다.

new Promise(function(resolve, reject) {
  resolve();
}); //콜백 함수의 인자 resolve를 아래와 같이 실행하면 이행(Fulfilled) 상태가 된다.

new Promise(function(resolve, reject) {
  reject();
}); // reject를 아래와 같이 호출하면 실패(Rejected) 상태가 된다.

function getData() {
  return new Promise(function(resolve, reject) {
    var data = 100;
    resolve(data);
  });
}  // promise 예시

Promise chaining

promise에서는 then 메소드를 이용하여 여러 개의 promise를 연결하여 사용할 수 있다. then() 메서드를 호출하고 나면 새로운 promise 객체가 반환된다.

function getData() {
  return new Promise({...});
}

// then() 으로 여러 개의 프로미스를 연결한 사용자 로그인 인증 로직 예시
getData(userInfo)
  .then(parseValue)
  .then(auth)
  .then(display);

위 예시처럼 유저 정보를 받고, 파싱하고, 인증을 받고, 화면에 나타내는 등의 작업을 then()을 통해 연결하여 차례대로 진행할 수 있다.

Promise error handling

promise의 에러 처리 방법에는 다음과 같이 2가지 방법이 있다.

getData().then(
  handleSuccess,
  handleError
); // 1. then()의 두 번째 인자로 에러를 처리하는 방법

getData().then().catch(); // 2.catch()를 이용하는 방법

이처럼 2가지 방법이 있지만, 가급적 catch()로 에러를 처리하는 게 더 효율적이다.

async await

async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법으로 이 특별한 문법을 사용하면 promise를 좀 더 편하게 사용할 수 있다.

async 함수

async function f() {
  return 1;
}

f().then(alert); // 1

function 앞에 async라는 예약어를 붙이면 해당 함수는 항상 promise를 반환한다. promise가 아닌 값을 반환하더라도 이행 상태의 promise(resolved promise)로 값을 감싸 이행된 promise가 반환되도록 한다.

await

await는 async 함수 안에서만 동작한다. javascript는 await 키워드를 만나면 promise가 처리(settled)될 때까지 기다린다. 결과는 그 이후 반환된다. promise가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });

  let result = await promise; // promise가 이행될 때까지 기다린다.

  alert(result); // "완료!"
}

f();

에러 핸들링

promise가 정상적으로 resolve되면 await promise는 promise 객체의 result에 저장된 값을 반환한다. 반면 promise가 reject되면 마치 throw문을 작성한 것처럼 에러가 던져진다.

async function f() {
  await Promise.reject(new Error("에러 발생!"));
}

await가 던진 에러는 throw가 던진 에러를 잡을 때처럼 try..catch를 사용해 잡을 수 있다.

async function f() {

  try {
    let response = await fetch('http://유효하지-않은-주소');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();
profile
재밌게 코딩하고 싶은 개발자!

1개의 댓글

comment-user-thumbnail
2021년 6월 27일

사진이 정말 맘에드네요

답글 달기