๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

[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โ€‹