동기(Synchronous) 프로그래밍 vs 비동기(Asynchronous) 프로그래밍
★ 동기 코드는 연산이 완료될 때 까지 기다린 후, 다음 코드라인을 실행한다.
즉, 다음 코드 라인은 연산이 완전히 완료된 후에만 실행된다.
각 연산이 순서대로 실행되기 때문에 코드의 흐름을 이해하기 쉽다는 장점이 있지만
대용량 파일 처리, 웹서버 요청, 소켓 통신 같이 시간이 오래걸리는 특정 작업의경우 전체 시스템이 차단될 수 있다는 단점이 있다.
★ 비동기 코드는 현재 연산이 완료되지 않더라도 request만 보내놓고, 다음 코드 라인을 실행할 수 있다.
I/O 작업, 네트워크 호출 같이 시간이 많이 소요되는 작업 수행 시 비동기 프로그램이 프로그램의 효율을 매우 높여준다.
복잡한 작업을 background에서 수행하고, 작업이 완료되면 callback, promise, async/await 등을 사용해 결과를 처리할 수 있다.
비동기 프로그래밍의 필요성
현대의 웹 애플리케이션은 사용자 요청을 신속하게 처리하면서 동시에 UI 응답성을 유지해야 한다.
비동기 프로그래밍은 네트워크 요청이나 파일 I/O, 데이터베이스 작업과 같이 시잔이 걸리는 작업들을 백그라운드에서 처리하여, 메인 실행 흐름을 방해하지 않고 결과를 처리할 수 있도록 한다.
JavaScript와 Promise
JavaScript에서 비동기 작업을 관리하는 가장 기본적인 방법 중 하나는 Promise 객체를 사용하는 것이다.
프로미스는 비동기 작업의 최종 완료 또는 실패를 나타내는 일종의 대기중인 값이다.
Promise의 상태 | |
Pending | 초기 상태, 성공 또는 실패가 결정되지 않음. |
Fulfilled | 연산이 성공적으로 완료됨 |
Rejected | 연산이 실패함 |
Promise Chaining
프로미스 체이닝은 여러 비동기 작업을 연결해 각 작업의 결과가 다음 작업의 입력으로 전달되도록 하는 기법이다.
이를 통해 복잡한 비동기 로직을 깔끔하게 순차적으로 표현할 수 있다.
동작 방식에 대한 예시는 다음과 같다.
아래 코드는...
✪ 비동기작업을 Promise 객체로 처리해 각 작업이 완료되기 전까지 메인 스레드를 블로킹하지 않고 다른 작업을 계속할 수 있다. 그리고 Promise 객체는 비동기 작업의 상태(대기, 이행, 거부)를 관리하기 때문에 각 작업이 완료되었는지 여부를 쉽게 확인할 수 있게 한다.
✪ downloadFile, readFile, modifyFile 이 세가지 함수가 Promise 객체를 반환하면서 이를 체이닝하여 순차적으로 비동기 작업을 처리한다. 즉, 프로미스 체이닝을 통해 각 작업이 완료된 후에 다음 작업을 실행할 수 있도록 보장한다.
프로미스 체이닝은 callback 지옥을 방지하고 비동기작업의 흐름을 더 명확하게 관리할 수 있게 한다. 작업이 순차적으로
실행되고 중간에 오류가 발생할 경우 'catch' 블록에서 처리해준다.
Callback 지옥?
1) 안좋은 예
asyncFunction1(function(result1) {
asyncFunction2(result1, function(result2) {
asyncFunction3(result2, function(result3) {
asyncFunction4(result3, function(result4) {
// 결과 처리
});
});
});
});
2) 좋은 예
asyncFunction1()
.then(result1 => asyncFunction2(result1))
.then(result2 => asyncFunction3(result2))
.then(result3 => asyncFunction4(result3))
.then(result4 => {
// 결과 처리
})
.catch(error => {
// 에러 처리
});
downloadFile 함수는 파일을 다운로드하고, 성공적으로 다운로드되면 다운로드 경로를 반환(reslove)한다.
그 후 반환된 경로는 readFile 함수에 전달되며, 이 함수는 해당 파일을 읽고 내용을 반환한다(파일 내용 resolve).
읽은 내용은 modifyFile 함수로 전달되어 수정되고, 수정된 내용이 반환된다(수정된 내용을 resolve).
// 첫 번째 프로미스: 파일 다운로드
function downloadFile(url) {
return new Promise((resolve, reject) => {
console.log(`Downloading file from ${url}`);
// 시뮬레이션을 위한 타이머
setTimeout(() => {
const filePath = 'local/path/to/file';
console.log(`Downloaded file to ${filePath}`);
resolve(filePath); // 파일 경로를 결과로 반환
}, 2000);
});
}
// 두 번째 프로미스: 파일 읽기
function readFile(path) {
return new Promise((resolve, reject) => {
console.log(`Reading file at ${path}`);
// 파일 읽기 시뮬레이션
setTimeout(() => {
const fileContents = 'File contents';
console.log(`Read file: ${fileContents}`);
resolve(fileContents); // 파일 내용을 결과로 반환
}, 1500);
});
}
// 세 번째 프로미스: 파일 내용 수정
function modifyFile(contents) {
return new Promise((resolve, reject) => {
console.log(`Modifying file contents`);
// 수정 작업 시뮬레이션
setTimeout(() => {
const newContents = contents + ' modified';
console.log(`Modified file contents: ${newContents}`);
resolve(newContents);
}, 1000);
});
}
// 프로미스 체이닝 사용
downloadFile('http://example.com/file')
.then(readFile)
.then(modifyFile)
.then((modifiedContents) => {
console.log(`Final modified contents: ${modifiedContents}`);
})
.catch((error) => {
console.error(`An error occurred: ${error}`);
});
async/await의 편리성
async/await 구문은 프로미스를 더 쉽게 작성하고 읽을 수 있도록 해주는 ES8 문법적 특징이다.
async 함수 안에서 await 키워드를 사용하면 JavaScript 엔진이 프로미스가 해결될 때까지 함수 실행을 자동으로 일시중지한다.
// async/await를 사용한 비동기 함수
async function processFile(url) {
try {
const filePath = await downloadFile(url);
const fileContents = await readFile(filePath);
const modifiedContents = await modifyFile(fileContents);
console.log(`Final modified contents: ${modifiedContents}`);
} catch (error) {
console.error(`An error occurred: ${error}`);
}
}
// 비동기 함수 호출
processFile('http://example.com/file');
위 코드에서 await 키워드는 downloadFile, readFile, modifyFile 함수가 반환하는 Promise가 해결될 때까지 함수 실행을 일시 중지하고, 그 결과를 변수에 할당한다. 각 비동기 작업이 완료될 때까지 기다린 후 다음 작업을 수행하게 한다.
✔비동기 프로그래밍에서의 에러 처리
비동기 프로그래밍의 프로미스 체인에서는 .catch() 메소드를 사용해 발생할 수 있는 오류를 적절히 관리해줘야 한다.
// 비동기 함수 정의
async function fetchData(url) {
try {
// await 키워드를 사용하여 fetch 요청의 Promise가 해결될 때까지 기다리는 부분
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data:", error);
}
}
// 함수 사용
const apiURL = "https://api.example.com/data";
fetchData(apiURL).then(data => {
console.log("Received data:", data);
});
Axios를 사용한 비동기 HTTP 요청
fetch API 외에도 axios 라이브러리를 이용해 HTTP reqeust를 수행할 수 있다.
axios는 자동으로 JSON 데이터 변환, 요청 취소, HTTP 요청에 대한 광범위한 설정 옵션을 제공한다.
위 fetch 메소드를 이용한 비동기 처리를 axios를 이용한 요청으로 바꾸게 되면 아래와 같다.
axios의 장점!!
👏 axios는 response.status가 200 범위가 아니면 자동으로 에러를 throw 하므로 별도의 상태 코드 체크가 불필요하다.
👏 axios는 자동으로 JSON 데이터를 파싱하여 반환한다.
👏axios는 네트워크 에러뿐만 아니라 HTTP 상태 코드 에러도 catch 블록에서 잡을 수 있다.
// 비동기 함수 정의
async function fetchData(url) {
try {.
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error("Error fetching data:", error.response ? error.response.data : error.message);
}
}
// 함수 사용
const apiURL = "https://api.example.com/data";
fetchData(apiURL).then(data => {
console.log("Received data:", data);
});
'Codeit Frontend PB' 카테고리의 다른 글
GIT 기본 명령어 정리 - 1 (0) | 2024.08.05 |
---|---|
Web API: Pagination (0) | 2024.07.21 |
Cookie, sessionStorage, localStorage에 관하여 (0) | 2024.07.13 |