이번 포스트에서는 ES Modules, Promise, async / await에 대해 포스팅 했습니다.
ES Modules(import/export)
파일을 기능별로 나누어 내보거나 가져올 수 있는 기능입니다.
export 에는 두 가지 방법이 있습니다.
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// App.js
import { add, multiply } from './math.js';
위 코드처럼 export 할 변수 앞에 export 만 적어주면 되고 여러개 가능합니다. 가져올때는 {} 안에 지정한 변수명을 입력하여 가져오면 됩니다.
(from 뒤의 './math.js'는 같은 폴더 내의 math.js 파일에서 가져오라는 뜻입니다. 또한, 확장자는 생략가능합니다.)
// Button.js
const Button = () => { ... };
export default Button;
// App.js
import MyButton from './Button.js'; // 'Button' 대신 'MyButton'으로 이름 지어도 됨
위 코드처럼 export 할 때는 export default라고 적어주면 되고 named 와 달리 한 파일내에 한 개만 가능합니다. 가져올 때는 {} 없이 원하는 이름으로 가져올 수 있습니다.
<MyComponent/>)Promise 객체
자바스크립트는 싱글 스레드라 한 번에 하나의 일만 할 수 있는데, 오래 걸리는 작업(네트워크 요청 등)을 기다리는 동안 화면이 멈추지 않게 하기 위해 비동기 처리가 필요하기 때문에 Promise를 사용합니다. 그래서 생기는 문제가 '데이터를 가져오는 동안 화면이 멈춰버리거나', '데이터 의존성'입니다. 이것을 해결하기 위한 방법이 Promise 입니다.
Promise는 3가지 상태를 가집니다.Pending (대기) : 작업 진행 중
Fulfilled (이행/성공) : 작업 성공
Rejected (거부/실패) : 작업 에러 발생
.then(): resolve가 호출되었을 때 실행됩니다. (성공 처리)
.catch(): reject가 호출되었을 때 실행됩니다. (실패 처리)
.finally(): 성공/실패 여부와 상관없이 마지막에 무조건 실행됩니다.
// Promise 생성
function userLoad(userId) {
return new Promise((resolve, reject) => {
console.log("데이터를 가져오는 중...");
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) {
// 성공하면 resolve를 호출하고 데이터를 담아 보냅니다. (상태: fulfilled)
resolve({ id: userId, name: "홍길동", age: 20 });
} else {
// 실패하면 reject를 호출하고 에러를 담아 보냅니다. (상태: rejected)
reject("서버 응답이 없습니다. 네트워크를 확인하세요.");
}
}, 1500);
});
}
// Promise 실행
userLoad(101)
.then((user) => {
// resolve()가 실행되면 여기로 들어옵니다.
// 인자로 넘어온 'user'는 resolve 안에 넣었던 객체입니다.
console.log("성공");
console.log(`유저 이름은 ${user.name}이고, 나이는 ${user.age}살입니다.`);
})
.catch((error) => {
// reject()가 실행되면 여기로 들어옵니다.
// 인자로 넘어온 'error'는 reject 안에서 쓴 문자열입니다.
console.log("실패");
console.log(`이유: ${error}`);
})
.finally(() => {
// 성공하든 실패하든 마지막에 실행됩니다.
console.log("모든 시도가 종료되었습니다.");
});
위 코드에서는 직접 생성자를 호출했지만 fetch 같은 일부 함수는 함수안에서 내부적으로 만들어줍니다.
fetch함수와async/await
이전 프로젝트에서 비동기 통신을 위해 JQuery의 AJAX를 사용했었어요. AJAX를 사용하려면 외부 라이브러리를 참조해야하고 라이브러리를 통째로 가져오는 것이기 때문에 아무래도 무거워요.
이런 단점을 보완할 수 있는게 fetch에요. 브라우저 내장 기능이기 때문에 가볍고 가독성 측면에서도 AJAX보다 좋아요.
위 단락에서 fetch 함수는 Promise를 포함하고 있다고 했었는데 그 덕분에 비동기 통신이 가능합니다. 다만, 단점이 로직이 복잡해지면 가독성과 유지보수성이 급격하게 떨어진다는 점이에요.
fetch('/user')
.then(res => res.json())
.then(user => {
// 여기서 user 객체 사용 가능
return fetch(`/posts?userId=${user.id}`);
})
.then(res => res.json())
.then(posts => {
// 위쪽 then 블록에서 끝났기 때문에 user 객체에 접근이 안 됨(따로 변수를 선언해둬야 함)
console.log(`${user.name}의 글:`, posts); // ReferenceError: user is not defined
});
위 코드에서 보이는 것처럼 Promise 체이닝(.then)이 생겨서 가독성과 유지보수가 힘들어지고 맨 아래 코드처럼 값의 공유가 힘들어집니다. 그래서 사용하는게 async/await 입니다.
async와 await의 쓰임새를 먼저 알아볼게요.
async : 일반 함수를 비동기 함수로 '정의'할때 사용합니다. (Promise 객체를 반환합니다.)
await : 비동기 작업(데이터를 불러오는 작업)을 기다렸다가 가져오도록 '명령'합니다.
위와 같이 쓰이기 때문에 fetch 앞에는 await가 붙습니다.
// 함수 앞에 async를 붙입니다.
async function fetchUserData() {
try {
// fetch 앞에 await를 붙입니다.
// 서버에서 응답이 올 때까지 여기서 잠시 멈춥니다.
const response = await fetch('https://api.example.com/user/1');
// '서버 에러'시에는 catch로 가지 않습니다. response.ok로 한번 걸러주는 작업이 필요합니다.
if (!response.ok) {
throw new Error("서버에 문제가 발생했습니다.");
}
// 응답이 왔다면, 데이터(JSON)를 꺼냅니다.
// 이 과정도 시간이 걸리므로 await를 붙여서 기다려줍니다.
const userData = await response.json();
console.log("사용자 정보:", userData);
} catch (error) {
// 네트워크가 끊기거나 주소가 잘못되는 등 에러가 나면 이곳이 동작합니다.)
console.error("데이터 가져오기 실패:", error);
}
}
Promise만 사용할 때보다 가독성이 향상되고 변수에 담을 수 있어 재사용에 용이합니다. 또한, try...catch를 사용할 수 있어 에러 처리에도 좋습니다.
이번 포스트를 마지막으로 [React 공부 전 알아야 할 JavaScript]에 대한 포스팅이 끝났습니다. 다음에 React 공부한 내용에 대해 포스팅 하겠습니다.