본문으로 건너뛰기

Promise

들어가며

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

콜백, 프로미스, 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

awaitasync 함수 내부에서만 동작해요. 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에 대해 알아봤어요. 자바스크립트 언어가 발전함에 따라 과거의 불편했던 점을 개선해나가는 모습을 볼 수 있어요. 과연 앞으로는 어떤 문법들이 등장할까요?