콜백이 싫어서 만들어진 Promise
들어가며
자바스크립트에서 비동기 작업은 어떻게 처리할 수 있을까요?
콜백, 프로미스, async/await.. 굉장히 많은 방법이 존재합니다. 그렇다면 이 키워드들은 어떤 특징을 가지고 있을까요? 이번 글에서는 각각의 특징에 대해 알아보려고 합니다.
콜백 함수란?
자바스크립트에는 콜백 함수라는 개념이 존재합니다.
콜백 함수는 "다른 함수에 인자로 전달되어, 특정 시점에 실행되는 함수" 를 말합니다. 쉽게 말해 "나중에 너가 할 일 다 끝나면 이 함수 좀 대신 실행해줘"라고 부탁하는 것과 같습니다. 콜백 함수라는 개념이 있는 이유는 자바스크립트의 함수가 일급 객체이기 때문입니다. 먼저 일급 객체에 대해 알아봅시다.
일급 객체
자바스크립트에서는 함수를 일급 객체로 취급합니다. 이는 함수를 값으로 다룰 수 있다는 뜻입니다.
일급 객체가 되기 위한 조건은 크게 세 가지입니다.
- 함수를 변수에 담을 수 있어야합니다.
- 다른 함수를 호출할 때 파라미터로 넘길 수 있어야합니다.
- 함수가 또 다른 함수를 반환할 수 있어야합니다.
콜백 함수를 통한 비동기 작업
아래 예시 코드를 살펴봅시다. 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에 대해 알아봤습니다. 자바스크립트 언어가 발전함에 따라 과거의 불편했던 점을 개선해나가는 모습을 볼 수 있습니다. 과연 앞으로는 어떤 문법들이 등장할까요?
