본문으둜 κ±΄λ„ˆλ›°κΈ°

[Medium] πŸ“„ Async/Await

πŸ’‘ λ¨Όμ € Promiseλ₯Ό 읽고 κΈ°λ³Έ κ°œλ…μ„ μ΄ν•΄ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€

async/awaitλž€ 무엇인가?​

async/awaitλŠ” ES2017 (ES8)μ—μ„œ λ„μž…λœ 문법적 섀탕(syntactic sugar)으둜, Promise μœ„μ— κ΅¬μΆ•λ˜μ–΄ 비동기 μ½”λ“œλ₯Ό 동기 μ½”λ“œμ²˜λŸΌ 보이게 ν•˜μ—¬ 읽기 쉽고 μœ μ§€λ³΄μˆ˜ν•˜κΈ° μ‰½κ²Œ λ§Œλ“€μ–΄μ€λ‹ˆλ‹€.

핡심 κ°œλ…:

  • async ν•¨μˆ˜λŠ” 항상 Promiseλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€
  • awaitλŠ” async ν•¨μˆ˜ λ‚΄μ—μ„œλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€
  • awaitλŠ” ν•¨μˆ˜ 싀행을 μΌμ‹œ μ€‘μ§€ν•˜κ³  Promiseκ°€ μ™„λ£Œλ  λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦½λ‹ˆλ‹€

κΈ°λ³Έ 문법​

async ν•¨μˆ˜β€‹

async ν‚€μ›Œλ“œλŠ” ν•¨μˆ˜κ°€ μžλ™μœΌλ‘œ Promiseλ₯Ό λ°˜ν™˜ν•˜κ²Œ ν•©λ‹ˆλ‹€:

// κΈ°μ‘΄ Promise μž‘μ„±λ²•
function fetchData() {
return Promise.resolve('자료');
}

// async μž‘μ„±λ²• (동일)
async function fetchData() {
return '자료'; // μžλ™μœΌλ‘œ Promise.resolve('자료')둜 λž˜ν•‘
}

// 호좜 방식은 동일
fetchData().then((data) => console.log(data)); // '자료'

await ν‚€μ›Œλ“œβ€‹

awaitλŠ” Promiseκ°€ μ™„λ£Œλ  λ•ŒκΉŒμ§€ 기닀리고 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€:

async function getData() {
const result = await Promise.resolve('μ™„λ£Œ');
console.log(result); // 'μ™„λ£Œ'
}

Promise vs async/await 비ꡐ​

예제 1: κ°„λ‹¨ν•œ API μš”μ²­β€‹

Promise μž‘μ„±λ²•:

function getUserData(userId) {
return fetch(`/api/users/${userId}`)
.then((response) => response.json())
.then((user) => {
console.log(user);
return user;
})
.catch((error) => {
console.error('였λ₯˜:', error);
throw error;
});
}

async/await μž‘μ„±λ²•:

async function getUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
console.log(user);
return user;
} catch (error) {
console.error('였λ₯˜:', error);
throw error;
}
}

예제 2: μ—¬λŸ¬ 비동기 μž‘μ—… 연쇄 싀행​

Promise μž‘μ„±λ²•:

function processUserData(userId) {
return fetchUser(userId)
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log(comments);
return comments;
})
.catch((error) => {
console.error('였λ₯˜:', error);
});
}

async/await μž‘μ„±λ²•:

async function processUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log(comments);
return comments;
} catch (error) {
console.error('였λ₯˜:', error);
}
}

μ—λŸ¬ μ²˜λ¦¬β€‹

try/catch vs .catch()​

async/awaitμ—μ„œ try/catch μ‚¬μš©:

async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('μš”μ²­ μ‹€νŒ¨:', error);
// μ—¬κΈ°μ„œ λ‹€μ–‘ν•œ μœ ν˜•μ˜ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€
if (error.name === 'NetworkError') {
// λ„€νŠΈμ›Œν¬ μ—λŸ¬ 처리
}
throw error; // λ‹€μ‹œ λ˜μ§€κ±°λ‚˜ κΈ°λ³Έκ°’ λ°˜ν™˜
}
}

ν˜Όν•© μ‚¬μš© (ꢌμž₯ν•˜μ§€ μ•Šμ§€λ§Œ μœ νš¨ν•¨):

async function fetchData() {
const response = await fetch('/api/data').catch((error) => {
console.error('μš”μ²­ μ‹€νŒ¨:', error);
return null;
});

if (!response) return null;

const data = await response.json();
return data;
}

닀쀑 try/catch​

μ„œλ‘œ λ‹€λ₯Έ λ‹¨κ³„μ˜ μ—λŸ¬μ— λŒ€ν•΄ 닀쀑 try/catchλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

async function complexOperation() {
let user;
try {
user = await fetchUser();
} catch (error) {
console.error('μ‚¬μš©μž κ°€μ Έμ˜€κΈ° μ‹€νŒ¨:', error);
return null;
}

try {
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error('κΈ€ κ°€μ Έμ˜€κΈ° μ‹€νŒ¨:', error);
return []; // κΈ°λ³Έκ°’μœΌλ‘œ 빈 λ°°μ—΄ λ°˜ν™˜
}
}

μ‹€μ œ μ‘μš© μ˜ˆμ œβ€‹

예제: 과제 채점 ν”„λ‘œμ„ΈμŠ€β€‹

ν”„λ‘œμ„ΈμŠ€: 과제 채점 β†’ 보상 확인 β†’ 보상 μ§€κΈ‰ β†’ 퇴학 λ˜λŠ” 처벌

// 과제 채점
function correctTest(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const score = Math.round(Math.random() * 100);
if (score >= 60) {
resolve({
name,
score,
});
} else {
reject('퇴학 기쀀에 λ„λ‹¬ν–ˆμŠ΅λ‹ˆλ‹€');
}
}, 2000);
});
}

// 보상 확인
function checkReward(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (data.score >= 90) {
resolve(`${data.name} μ˜ν™” ν‹°μΌ“ νšλ“`);
} else if (data.score >= 60 && data.score < 90) {
resolve(`${data.name} ν‘œμ°½ νšλ“`);
} else {
reject('보상이 μ—†μŠ΅λ‹ˆλ‹€');
}
}, 2000);
});
}

Promise μž‘μ„±λ²•:

correctTest('John Doe')
.then((data) => checkReward(data))
.then((reward) => console.log(reward))
.catch((error) => console.log(error));

async/await λ¦¬νŒ©ν† λ§:

async function processStudent(name) {
try {
const data = await correctTest(name);
const reward = await checkReward(data);
console.log(reward);
return reward;
} catch (error) {
console.log(error);
return null;
}
}

processStudent('John Doe');

예제: μ—¬λŸ¬ μš”μ²­ λ™μ‹œ 싀행​

μ—¬λŸ¬ μš”μ²­ 간에 의쑴 관계가 없을 λ•ŒλŠ” λ™μ‹œμ— μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€:

❌ 잘λͺ»λœ 방법: 순차 μ‹€ν–‰ (느림):

async function fetchAllData() {
const users = await fetchUsers(); // 1초 λŒ€κΈ°
const posts = await fetchPosts(); // 또 1초 λŒ€κΈ°
const comments = await fetchComments(); // 또 1초 λŒ€κΈ°
// 총 3초
return { users, posts, comments };
}

βœ… μ˜¬λ°”λ₯Έ 방법: λ™μ‹œ μ‹€ν–‰ (빠름):

async function fetchAllData() {
// μ„Έ μš”μ²­μ„ λ™μ‹œμ— μ‹œμž‘
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
// 1초만 ν•„μš” (κ°€μž₯ 느린 μš”μ²­μ˜ μ‹œκ°„)
return { users, posts, comments };
}

Promise.allSettled()둜 λΆ€λΆ„ μ‹€νŒ¨ 처리:

async function fetchAllData() {
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);

const users = results[0].status === 'fulfilled' ? results[0].value : [];
const posts = results[1].status === 'fulfilled' ? results[1].value : [];
const comments = results[2].status === 'fulfilled' ? results[2].value : [];

return { users, posts, comments };
}

ν”ν•œ μ‹€μˆ˜β€‹

1. λ°˜λ³΅λ¬Έμ—μ„œ await μ‚¬μš© (순차 μ‹€ν–‰)​

❌ 잘λͺ»λœ 방법: 맀번 λ°˜λ³΅λ§ˆλ‹€ λŒ€κΈ°, λΉ„νš¨μœ¨μ :

async function processUsers(userIds) {
const results = [];
for (const id of userIds) {
const user = await fetchUser(id); // 순차 μ‹€ν–‰, 맀우 느림!
results.push(user);
}
return results;
}
// μ‚¬μš©μžκ°€ 10λͺ…이고, 각 μš”μ²­μ΄ 1초이면, 총 10초 μ†Œμš”

βœ… μ˜¬λ°”λ₯Έ 방법: Promise.all()둜 λ™μ‹œ μ‹€ν–‰:

async function processUsers(userIds) {
const promises = userIds.map((id) => fetchUser(id));
const results = await Promise.all(promises);
return results;
}
// 10λͺ…μ˜ μ‚¬μš©μžλ₯Ό λ™μ‹œ μš”μ²­, 1초만 μ†Œμš”

μ ˆμΆ©μ•ˆ: λ™μ‹œ μ‹€ν–‰ 수 μ œν•œ:

async function processUsersWithLimit(userIds, limit = 3) {
const results = [];
for (let i = 0; i < userIds.length; i += limit) {
const batch = userIds.slice(i, i + limit);
const batchResults = await Promise.all(batch.map((id) => fetchUser(id)));
results.push(...batchResults);
}
return results;
}
// ν•œ λ²ˆμ— 3κ°œμ”© 처리, λ„ˆλ¬΄ λ§Žμ€ μš”μ²­μ„ ν•œκΊΌλ²ˆμ— λ³΄λ‚΄λŠ” 것을 λ°©μ§€

2. await μ‚¬μš© μžŠμ–΄λ²„λ¦¬κΈ°β€‹

awaitλ₯Ό 잊으면 μ‹€μ œ 값이 μ•„λ‹Œ Promiseλ₯Ό λ°›κ²Œ λ©λ‹ˆλ‹€:

// ❌ 잘λͺ»λœ 방법
async function getUser() {
const user = fetchUser(1); // await 잊음, userλŠ” Promise
console.log(user.name); // undefined (Promiseμ—λŠ” name 속성이 μ—†μŒ)
}

// βœ… μ˜¬λ°”λ₯Έ 방법
async function getUser() {
const user = await fetchUser(1);
console.log(user.name); // μ˜¬λ°”λ₯Έ 이름
}

3. async 없이 await μ‚¬μš©β€‹

awaitλŠ” async ν•¨μˆ˜ λ‚΄μ—μ„œλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

// ❌ 잘λͺ»λœ 방법: 문법 μ—λŸ¬
function getData() {
const data = await fetchData(); // SyntaxError
return data;
}

// βœ… μ˜¬λ°”λ₯Έ 방법
async function getData() {
const data = await fetchData();
return data;
}

μ΅œμƒμœ„ await (Top-level await):

ES2022와 λͺ¨λ“ˆ ν™˜κ²½μ—μ„œλŠ” λͺ¨λ“ˆ μ΅œμƒμœ„μ—μ„œ awaitλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

// ES2022 module
const data = await fetchData(); // λͺ¨λ“ˆ μ΅œμƒμœ„μ—μ„œ μ‚¬μš© κ°€λŠ₯
console.log(data);

4. μ—λŸ¬ 처리 λˆ„λ½β€‹

try/catchκ°€ μ—†μœΌλ©΄ μ—λŸ¬κ°€ μž‘νžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€:

// ❌ μž‘νžˆμ§€ μ•ŠλŠ” μ—λŸ¬ λ°œμƒ κ°€λŠ₯
async function fetchData() {
const response = await fetch('/api/data'); // μ‹€νŒ¨ν•˜λ©΄ μ—λŸ¬λ₯Ό 던짐
return response.json();
}

// βœ… μ—λŸ¬ 처리 μΆ”κ°€
async function fetchData() {
try {
const response = await fetch('/api/data');
return response.json();
} catch (error) {
console.error('였λ₯˜:', error);
return null; // λ˜λŠ” κΈ°λ³Έκ°’ λ°˜ν™˜
}
}

5. async ν•¨μˆ˜λŠ” 항상 Promiseλ₯Ό λ°˜ν™˜β€‹

awaitλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šλ”λΌλ„ async ν•¨μˆ˜λŠ” Promiseλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€:

async function getValue() {
return 42; // μ‹€μ œλ‘œλŠ” Promise.resolve(42)λ₯Ό λ°˜ν™˜
}

// .then() λ˜λŠ” await둜 값을 가져와야 함
getValue().then((value) => console.log(value)); // 42

// λ˜λŠ”
async function printValue() {
const value = await getValue();
console.log(value); // 42
}

κ³ κΈ‰ μ‘μš©β€‹

νƒ€μž„μ•„μ›ƒ μ²˜λ¦¬β€‹

Promise.race()λ₯Ό μ‚¬μš©ν•œ νƒ€μž„μ•„μ›ƒ λ©”μ»€λ‹ˆμ¦˜ κ΅¬ν˜„:

function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('μš”μ²­ μ‹œκ°„ 초과')), ms);
});
}

async function fetchWithTimeout(url, ms = 5000) {
try {
const response = await Promise.race([fetch(url), timeout(ms)]);
return await response.json();
} catch (error) {
console.error('μš”μ²­ μ‹€νŒ¨:', error.message);
throw error;
}
}

// μ‚¬μš©
fetchWithTimeout('/api/data', 3000); // 3초 νƒ€μž„μ•„μ›ƒ

μž¬μ‹œλ„ λ©”μ»€λ‹ˆμ¦˜β€‹

μ‹€νŒ¨ μ‹œ μžλ™ μž¬μ‹œλ„ κ΅¬ν˜„:

async function fetchWithRetry(url, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === retries - 1) throw error; // λ§ˆμ§€λ§‰ μž¬μ‹œλ„ μ‹€νŒ¨, μ—λŸ¬ λ˜μ§€κΈ°

console.log(`${i + 1}번째 μ‹œλ„ μ‹€νŒ¨, ${delay}ms ν›„ μž¬μ‹œλ„...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}

// μ‚¬μš©
fetchWithRetry('/api/data', 3, 2000); // μ΅œλŒ€ 3회 μž¬μ‹œλ„, 2초 간격

순차 μ²˜λ¦¬ν•˜λ©΄μ„œ μƒνƒœ μœ μ§€β€‹

순차적으둜 μ‹€ν–‰ν•˜λ˜ λͺ¨λ“  κ²°κ³Όλ₯Ό 보쑴해야 ν•  λ•Œ:

async function processInOrder(items) {
const results = [];

for (const item of items) {
const result = await processItem(item);
results.push(result);

// 이전 결과에 따라 λ‹€μŒ 단계λ₯Ό κ²°μ •ν•  수 있음
if (result.shouldStop) {
break;
}
}

return results;
}

Event Loopμ—μ„œμ˜ async/await​

async/awaitλŠ” 본질적으둜 μ—¬μ „νžˆ Promiseμ΄λ―€λ‘œ λ™μΌν•œ Event Loop κ·œμΉ™μ„ λ”°λ¦…λ‹ˆλ‹€:

console.log('1');

async function test() {
console.log('2');
await Promise.resolve();
console.log('3');
}

test();

console.log('4');

// 좜λ ₯ μˆœμ„œ: 1, 2, 4, 3

뢄석:

  1. console.log('1') - 동기 μ‹€ν–‰
  2. test()κ°€ 호좜되고, console.log('2') - 동기 μ‹€ν–‰
  3. await Promise.resolve() - 이후 μ½”λ“œλ₯Ό micro task에 μΆ”κ°€
  4. console.log('4') - 동기 μ‹€ν–‰
  5. micro task μ‹€ν–‰, console.log('3')

λ©΄μ ‘ 핡심 ν¬μΈνŠΈβ€‹

  1. async/awaitλŠ” Promise의 문법적 섀탕: 더 읽기 μ‰½μ§€λ§Œ λ³Έμ§ˆμ€ 동일
  2. μ—λŸ¬ μ²˜λ¦¬λŠ” try/catch μ‚¬μš©: .catch() λŒ€μ‹ 
  3. λ™μ‹œ μ‹€ν–‰ vs 순차 싀행에 주의: λ°˜λ³΅λ¬Έμ—μ„œ λ¬΄λΆ„λ³„ν•˜κ²Œ await μ‚¬μš©ν•˜μ§€ μ•ŠκΈ°
  4. async ν•¨μˆ˜λŠ” 항상 Promiseλ₯Ό λ°˜ν™˜: λͺ…μ‹œμ μœΌλ‘œ return Promiseν•˜μ§€ μ•Šμ•„λ„
  5. awaitλŠ” async ν•¨μˆ˜ λ‚΄μ—μ„œλ§Œ μ‚¬μš© κ°€λŠ₯: μ΅œμƒμœ„ await (ES2022) μ œμ™Έ
  6. Event Loop 이해: await μ΄ν›„μ˜ μ½”λ“œλŠ” micro task

κ΄€λ ¨ μ£Όμ œβ€‹

  • Promise - async/await의 기초
  • Event Loop - μ‹€ν–‰ μˆœμ„œ 이해

Reference​