[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
๋ถ์:
console.log('1')- ๋๊ธฐ ์คํtest()๊ฐ ํธ์ถ๋๊ณ ,console.log('2')- ๋๊ธฐ ์คํawait Promise.resolve()- ์ดํ ์ฝ๋๋ฅผ micro task์ ์ถ๊ฐconsole.log('4')- ๋๊ธฐ ์คํ- micro task ์คํ,
console.log('3')
๋ฉด์ ํต์ฌ ํฌ์ธํธโ
- async/await๋ Promise์ ๋ฌธ๋ฒ์ ์คํ: ๋ ์ฝ๊ธฐ ์ฝ์ง๋ง ๋ณธ์ง์ ๋์ผ
- ์๋ฌ ์ฒ๋ฆฌ๋ try/catch ์ฌ์ฉ:
.catch()๋์ - ๋์ ์คํ vs ์์ฐจ ์คํ์ ์ฃผ์: ๋ฐ๋ณต๋ฌธ์์ ๋ฌด๋ถ๋ณํ๊ฒ await ์ฌ์ฉํ์ง ์๊ธฐ
- async ํจ์๋ ํญ์ Promise๋ฅผ ๋ฐํ: ๋ช ์์ ์ผ๋ก return Promiseํ์ง ์์๋
- await๋ async ํจ์ ๋ด์์๋ง ์ฌ์ฉ ๊ฐ๋ฅ: ์ต์์ await (ES2022) ์ ์ธ
- Event Loop ์ดํด: await ์ดํ์ ์ฝ๋๋ micro task
๊ด๋ จ ์ฃผ์ โ
- Promise - async/await์ ๊ธฐ์ด
- Event Loop - ์คํ ์์ ์ดํด