비동기 프로그래밍 - JavaScript - async / await
2024. 06. 09.
비동기 프로그래밍
이어서
이전 포스팅까지 해서 Promise를 활용한 비동기 프로그래밍에 대해서 알아봤습니다. 마지막으로 async / await 에 대해 알아보도록 하겠습니다.
async / await?
async / await는 비교적 최신 JavaScript 문법입니다.
많은 사람들이 Java의 버전 중 큰 분기점이 되는 버전으로 Java 8을 떠올리는데요, 이에 대응하는 JavaScript의 큰 전환점이 ES6였습니다.
async / await 도 이 ES6에서 소개된 문법입니다.
async
는 함수의 선언부에 붙일 수 있는 구문입니다. async
가 붙은 함수는 어떤 값을 반환하더라도 Promise를 반환하게 됩니다. 또한 await
는 Promise
의 호출부에 사용할 수 있는 구문입니다. await
가 붙은 Promise는 fulfilled 혹은 rejected 상태가 될 때까지 기존 실행 흐름을 멈추고 대기하게 됩니다.
DB 접근 예제
async / await를 사용하게 되면 Promise를 사용할 때와 어떤 차이점이 있을지 지금까지 사용해오던 예제를 그대로 활용하여 알아보도록 하겠습니다.
DB에 쿼리를 날려 특정 유저가 작성한 게시글과 그 게시글의 댓글을 불러와보자.
앞 포스트들에서 했던 것과 구현 자체가 크게 다르지 않기 때문에 전체 코드를 보며 차이점얼 알아보도록 하겠습니다.
const database = {
users: [
{ id: 1, name: "John", age: 30 },
{ id: 2, name: "Jane", age: 25 },
{ id: 3, name: "Bob", age: 40 },
],
posts: [
{ id: 1, title: "Post 1", body: "Lorem ipsum", userId: 1 },
{ id: 2, title: "Post 2", body: "Dolor sit amet", userId: 2 },
{ id: 3, title: "Post 3", body: "Consectetur adipiscing elit", userId: 1 },
],
comments: [
{ id: 1, body: "Great post!", postId: 1 },
{ id: 2, body: "Thanks for sharing", postId: 2 },
{ id: 3, body: "I learned a lot from this", postId: 1 },
],
};
async function findUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = database.users.find((user) => user.id === userId);
if (!user) {
reject(`Invalid user id: ${userId}`);
return;
}
resolve(user);
}, 1000);
});
}
async function findPostsByUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const posts = database.posts.filter((post) => post.userId === userId);
if (posts.length === 0) {
reject(`No posts found for user id: ${userId}`);
return;
}
resolve(posts);
}, 1000);
});
}
async function findCommentsByPost(postId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const comments = database.comments.filter((comment) => comment.postId === postId);
resolve(comments);
}, 1000);
});
}
async function getUsersPostsCommentsAsync(userId) {
try {
const user = await findUser(userId);
const posts = await findPostsByUser(userId);
const postsWithComments = await Promise.all(posts.map(async (post) => {
const comments = await findCommentsByPost(post.id);
return { ...post, comments };
}));
return { user, posts: postsWithComments };
} catch (error) {
console.error(error);
}
}
getUsersPostsCommentsAsync(1)
.then((result) => {
console.log(result);
});
일단 각 함수에 async
키워드가 붙어있는 것을 볼 수 있습니다. 사실 예제 코드에서는 마지막 구현부인 getUsersPostsCommentsAsync()
를 제외하고는 이 키워드가 있는지의 여부는 큰 영향을 미치지 않습니다. 함수 내부에서 명시적으로 Promise를 반환하고 있기 때문입니다.
async
함수는 일반 값을 반환하더라도 그 값이 Promise로 감싸져서 반환되기 때문입니다. 이는 동기적으로 로직을 작성했을 경우 Promise를 resolve 하여 반환하는 것과 같은 동작을 수행한다고 생각해볼 수도 있습니다.
또한 async 함수에서 예외가 발생하면(throw Error()
) 그것은 Promise가 reject 되는 것과 같은 영향을 미치게 됩니다. 아래 예제 코드를 보시면 조금 더 이해가 빠르실 것 같습니다.
async function foo() {
return 100;
}
foo().then((num) => console.log(100));
// Output
// 100
async function bar() {
throw Error("some error occurred from bar");
}
bar()
.then(() => console.log("success"))
.catch((e) => console.error(e));
// Output
// Error: some error occurred from bar
// at bar (/example/index.js:8:9)
// at Object.<anonymous> (/example/index.js:11:1)
// at Module._compile (node:internal/modules/cjs/loader:1159:14)
// at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
// at Module.load (node:internal/modules/cjs/loader:1037:32)
// at Module._load (node:internal/modules/cjs/loader:878:12)
// at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
// at node:internal/main/run_main_module:23:47
이 코드에서 bar()
로부터 reject가 발생할 경우, console.error(e)
하게 되어 있는데요. 보시다시피 async
함수에서 예외가 발생하니 해당 핸들러에 잡히는 것을 알 수 있습니다.
async / await 그리고 Promise
위의 내용을 토대로 볼 때, async
와 await
를 활용하면 Promise에 의해 비동기적으로 동작하던 코드를 조금 더 동기적인 관점으로 바라볼 수 있다는 것을 알 수 있습니다.
특히 예제의 구현부를 보면, Promise를 다루기 위한 보일러플레이트가 없어지면서 코드 자체가 크게 컴팩트해졌다는 사실을 역시 볼 수 있습니다. 이 코드의 컴팩트함에 크게 기여하는 것은 await
라고 볼 수 있는데요, Promise가 정상적으로 완료되어 fulfil될 경우의 값을 await
를 통해 받아온다고 볼 수 있습니다.
마치며
간단한 JavaScript 비동기 프로그래밍 글을 3개 쓰는 데에 1년이나 걸렸네요. 도움이 되었으면 좋겠는데, 아쉽게도 개인적인 개념 정리에 그친 것 같긴 합니다. 저는 업무에선 대부분 async / await를 활용하여 로직을 작성하는 것을 선호합니다. 이 편이 로직의 가독성을 높이는 것에 큰 도움을 준다고 생각하기 때문입니다. 다만 async / await는 Promise를 활용하는 더 나은 방법을 제공한다 정도로 생각하고, Promise에 대한 전반적인 이해를 항상 가지고 있는 것이 중요할 것 같습니다.
감사합니다.
끝.