프로그래밍 공부/Javascript

[JS/Node.js] Callback, Promise, async/await / TIL 28일차

Kevinkb 2021. 10. 16. 17:53

자바스크립트의 비동기 처리를 살펴보면 다음과 같다.

비동기 흐름을 예측하기 어렵다.

// #1
console.log('Hello');
// #2
setTimeout(function() {
    console.log('Bye');
}, 3000);
// #3
console.log('Hello Again');

// 'Hello'
// 'Hello Again'
// 3초 뒤에
// 'Bye'

의도치 않게 동작한다.

function getData() {
    var tableData;
    $.get('https://domain.com/products/1', function(response) {
        tableData = response;
    });
    return tableData;
}

console.log(getData()); // undefined

자바스크립트는 비동기 처리가 필수적이지만 이러한 불특정 흐름은 통제되야 한다. 이를 위해 3가지 방법을 사용할 수 있다.

Callback

callback 함수를 이용해 비동기 흐름을 조절할 수 있다.

function getData(callbackFunc) {
    $.get('https://domain.com/products/1', function(response) {
        callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
    });
}

getData(function(tableData) {
    console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});

하지만 함수가 언제 끝날지 예측할 수 없기 때문에, 모든 종속 함수를 중첩시켜야 한다. 이를 콜백 지옥이라 한다.

$.get('url', function(response) {
    parseValue(response, function(id) {
        auth(id, function(result) {
            display(result, function(text) {
                console.log(text);
            });
        });
    });
});

이러한 콜백 지옥은 코딩 패턴으로 해결 가능하지만 Promise나 Async를 이용해 더 편하게 구현할 수 있다.

// 코딩 패턴으로 극복
function parseValueDone(id) {
    auth(id, authDone);
}
function authDone(result) {
    display(result, displayDone);
}
function displayDone(text) {
    console.log(text);
}
$.get('url', function(response) {
    parseValue(response, parseValueDone);
});

Promise

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
Promise는 Callback 함수를 인자로 받고, 상태([[PromiseStatus]])와 값([[PromiseValue]])를 포함하는 객체입니다.

Promise Callback

Promise Callback은 2가지 인자를 받는데 첫번째 인자는 resolve 메소드로 Promise 객체의 콜백함수가 제대로 수행(fulfilled) 되었을 때 호출되는 메소드입니다. 두번째 인자는 reject 인자로 콜백함수가 거부(rejected) 되었을 때 호출됩니다.

new Promise((resolve, rejected) => {
    if ('에러가 날 경우') {
        rejected(err)
    } else {
        // 데이터를 받을 경우
        resolve(data)
    }
})

Promise Status

  1. Pending(대기)
  2. Fulfilled(이행)
  3. Rejected(실패)

Error 처리

  1. then()의 콜백함수의 두번째 파라미터로 처리 => then() 내부에서 일어나는 에러를 처리할 수 없다.
  2. catch()를 사용한 처리
// then()의 두 번째 인자로는 감지하지 못하는 오류
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result);
  throw new Error("Error in then()"); // Uncaught (in promise) Error: Error in then()
}, function(err) {
  console.log('then error : ', err);
});
// catch()로 오류를 감지하는 코드
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result); // hi
  throw new Error("Error in then()");
}).catch(function(err) {
  console.log('then error : ', err); // then error :  Error: Error in then()
});

즉, catch()를 사용하는 것이 더 유리하다.


async & await

기본 문법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있다.

// 일반 프로미스
function runPromise() {
  resetTitle();
  playVideo();

  sleep(1000).then(() => {
    pauseVideo();
    displayTitle();
  })
    .then(sleep.bind(null, 500))
    .then(highlightTitle)
    .then(sleep.bind(null, 2000))
    .then(resetTitle)
}
// async/await 프로미스
async function runAsync() {
  resetTitle();
  playVideo();

  await sleep(1000);
  pauseVideo();
  displayTitle();

  await sleep(500);
  highlightTitle();

  await sleep(2000);
  resetTitle();
}

예외 처리 => try, catch문 사용

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title); // delectus aut autem
    }
  } catch (error) {
    console.log(error);
  }
}