본문으로 건너뛰기

콜백이 싫어서 만들어진 Promise

· 약 4분
김현재
개발자

들어가며​

자바스크립트에서 비동기 작업은 어떻게 처리할 수 있을까요?

콜백, 프로미스, async/await.. 굉장히 많은 방법이 존재합니다. 그렇다면 이 키워드들은 어떤 특징을 가지고 있을까요? 이번 글에서는 각각의 특징에 대해 알아보려고 합니다.

콜백 함수란?​

자바스크립트에는 콜백 함수라는 개념이 존재합니다.

콜백 함수는 "다른 함수에 인자로 전달되어, 특정 시점에 실행되는 함수" 를 말합니다. 쉽게 말해 "나중에 너가 할 일 다 끝나면 이 함수 좀 대신 실행해줘"라고 부탁하는 것과 같습니다. 콜백 함수라는 개념이 있는 이유는 자바스크립트의 함수가 일급 객체이기 때문입니다. 먼저 일급 객체에 대해 알아봅시다.

일급 객체​

자바스크립트에서는 함수를 일급 객체로 취급합니다. 이는 함수를 값으로 다룰 수 있다는 뜻입니다. 일급 객체가 되기 위한 조건은 크게 세 가지입니다.

  1. 함수를 변수에 담을 수 있어야합니다.
  2. 다른 함수를 호출할 때 파라미터로 넘길 수 있어야합니다.
  3. 함수가 또 다른 함수를 반환할 수 있어야합니다.

콜백 함수를 통한 비동기 작업​

아래 예시 코드를 살펴봅시다. setTimeout을 이용해 1초마다 숫자를 1씩 증가시키는 코드입니다.

function increase(count, callback) {
setTimeout(() => {
const increased = count + 1;
console.log(increased);
if (callback) {
callback(increased);
}
}, 1000);
}

increase(0, (count) => {
increase(count, (count) => {
increase(count, (count) => {
increase(count, (count) => {
console.log('종료');
});
});
});
});

increase 함수는 1초를 기다린 후, 계산이 끝나면 넘겨준 callback을 호출하며 결과값을 전달합니다.

Promise란?​

프로미스는 자바스크립트의 객체로 비동기 작업의 완료 또는 실패를 나타내는 객체입니다. 프로미스는 다음 중 하나의 상태를 가질 수 있습니다.

  • 대기(pending) : 초기 상태
  • 이행(fulfilled) : 연산이 성공적으로 완료됨
  • 거부(rejected) : 연산이 실패함

Promise로 비동기 처리하기​

프로미스 객체는 new 키워드와 함께 Promise 생성자 함수를 이용하여 생성할 수 있습니다. 또한 프로미스 생성자의 매개변수로 두 개의 매개변수를 가진 콜백 함수를 넣을 수 있습니다. 매개변수는 각각 작업이 성공했을 때의 resolve 객체, 작업이 실패했을 때의 reject 객체입니다.

const promise = new Promise((resolve, reject) => {
// 비동기 작업

if (성공) {
resolve();
} else {
reject;
}
});

우리가 자주 사용하는 fetch 메서드 또한 프로미스를 반환한다는 사실을 알고 계셨나요? 아래 예제는 https://jsonplaceholder.typicode.com/ 를 이용해서 작성했습니다. API 요청 테스트가 필요할 때 사용하시면 좋은 서비스입니다.

function getUser() {
return fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => {
if (!response.ok) {
throw new Error('API 요청 실패');
}
return response.json();
});
}

function getPost(userId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${userId}`).then((response) => {
if (!response.ok) {
throw new Error('API 요청 실패');
}
return response.json();
});
}

function getComment(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`).then((response) => {
if (!response.ok) {
throw new Error('API 요청 실패');
}
return response.json();
});
}

getUser().then(({ userId }) =>
getPost(userId)
.then(({ id }) => getComment(id))
.then((comment) => console.log(comment)),
);

이렇게 작성된 함수들을 프로미스 체인(then)을 이용하여 비동기 작업을 처리할 수 있습니다.

then() 함수는 새로운 프로미스를 반환합니다.

async/await​

async/await을 사용하면 프로미스를 더욱 편하게 사용할 수 있습니다. async/await은 프로미스의 문법적 설탕(Syntax Sugar)입니다.

문법적 설탕(Syntax Sugar)은 기능은 그대로 유지하지만, 가독성을 높이고 더 간결하게 사용할 수 있도록 변경하는 것을 의미합니다.

async 함수​

async 키워드를 function 앞에 위치시키면 해당 함수는 항상 프로미스를 반환합니다. 만약에 함수 내부에서 강제로 프로미스가 아닌 값을 반환하게 하더라도 이행된 프로미스를 반환합니다.

async function foo() {
return 1;
}

foo().then((data) => console.log(data)); // 1

await​

await은 async 함수 내부에서만 동작합니다. await 키워드를 만나면 프로미스가 처리될 때까지 기다리고 결과는 그 이후에 반환됩니다.

그렇다면 위에 프로미스로 작성한 코드를 async 함수들로 변경하면 아래와 같습니다.

async function getUser() {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);

if (!response.ok) {
throw new Error('API 요청 실패');
}

return response.json();
}

async function getPost(userId) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${userId}`);

if (!response.ok) {
throw new Error('API 요청 실패');
}

return response.json();
}

async function getComment(postId) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`);

if (!response.ok) {
throw new Error('API 요청 실패');
}

return response.json();
}

이후 이 함수들을 순차적으로 실행시키기 위한 메인 함수도 하나 만들어 주겠습니다.

async function main() {
try {
const userData = await getUser();
const postData = await getPost(userData.id);
const commentData = await getComment(postData.id);

console.log(commentData);
} catch (error) {
console.error('Error: ', error);
}
}

main();

위처럼 작성된 코드는 우리가 원하던 대로 비동기 코드이지만 순차적으로 실행되게 됩니다.

용어 정리​

지금까지 학습한 내용을 간단하게 정리해보겠습니다.

Promise​

프로미스란 비동기 작업의 완료 또는 실패를 나타내는 객체입니다. 과거 비동기 작업을 콜백으로 처리했을 때 발생했던 콜백 지옥 문제와 중첩된 에러 핸들링 문제를 then 메서드를 활용해 프로미스 체이닝으로 해결할 수 있습니다. 또한 매개변수로 주어진 콜백은 주어진 순서대로 실행되는 것을 보장합니다.

async/await​

function 키워드 앞에 async 키워드를 붙여 async 함수를 생성할 수 있습니다. await 키워드는 반드시 async 함수 내부에서만 사용이 가능하며 try/catch 문을 통해 에러를 핸들링합니다.

마무리​

이번 글에서는 자바스크립트에서 비동기를 처리하기 위해 사용하는 콜백 함수, 프로미스, async/await에 대해 알아봤습니다. 자바스크립트 언어가 발전함에 따라 과거의 불편했던 점을 개선해나가는 모습을 볼 수 있습니다. 과연 앞으로는 어떤 문법들이 등장할까요?