취준 시절에 정리하였으나, 지금보니 아쉬운 내용들이 많아서 다시 수정하려고 적었다.
먼저 사용하는 이유를 정리하고 하나씩 풀어가겠다
실행 순서를 보장 받기 위함이다.
이전에 헷갈렸던 점은 비동기의 장점까지 같이 생각을 했었다.
블로킹을 막는 것이 비동기의 장점이고, 이 비동기와 실행순서를 보장해주는 것이다.
콜백 및 promise, async/await는 비동기와 같이 실행순서를 보장할 수 없는 것들의 실행순서를 보장하고 균일한 서비스를 제공하기 위해 사용한다.
이제 하나하나의 장점을 살펴보자
JavaScript에서 콜백은 함수를 다른 함수의 인자(Argument)로 전달합니다.
비동기 작업 완료 후 실행해야 하는 코드를 지정하기 위함입니다.
예를 들어, 파일을 읽거나 네트워크 요청이 완료된 후에 특정 작업을 수행해야 할 때 사용합니다.
이벤트 리스너, setTimeout, 파일 읽기 등 비동기적 작업 처리에 많이 사용합니다.
function callbackFunction(result) {
// 콜백 함수에서 수행할 작업: 결과 출력
console.log("결과:", result);
}
function doSomething(callback) {
// 어떤 작업 수행: 데이터 처리
const data = "처리된 데이터";
console.log("doSomething에서 작업 수행");
// 전달받은 콜백 함수 실행, 처리된 데이터를 인자로 전달
callback(data);
}
doSomething(callbackFunction); // 콜백 함수를 인자로 전달하여 doSomething 함수 호출
콜백 안에 또 다른 콜백을 계속 넣어야 하는 상황이 발생할 수 있으며, 이는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만듭니다.
// 예를 들어, 첫 번째 데이터를 가져오는 함수
function getData(callback) {
setTimeout(() => {
callback('첫 번째 데이터');
}, 1000);
}
// 추가 데이터를 가져오는 함수
function getMoreData(input, callback) {
setTimeout(() => {
callback(input + ' + 추가 데이터');
}, 1000);
}
// 중첩된 콜백을 사용하여 연속적인 데이터 처리
getData(function(a){
console.log(a); // 첫 번째 데이터
getMoreData(a, function(b){
console.log(b); // 첫 번째 데이터 + 추가 데이터
getMoreData(b, function(c){
console.log(c); // 두 번째 추가 데이터
getMoreData(c, function(d){
console.log(d); // 세 번째 추가 데이터
getMoreData(d, function(e){
console.log(e); // 네 번째 추가 데이터
// 여기서 더 많은 중첩 콜백이 이어질 수 있음
});
});
});
});
});
프로미스는 비동기 작업의 성공(resolve) 또는 실패(reject)와 그 결과값을 나타내는 객체입니다.
등장 년도: 2015년, ECMAScript 2015(ES6)에 공식적으로 도입됨.
ECMAScript는 자바스크립트(Javascript)의 표준 사양입니다. 자바스크립트를 개발하고 표준화하는 데 사용되는 언어의 규격입니다. ECMAScript는 웹 브라우저가 자바스크립트를 어떻게 구현해야 하는지에 대한 지침을 제공합니다.
비동기 작업의 결과를 나중에 처리할 수 있도록 하는 것입니다. 콜백보다 깔끔하고 구조화한 방법(에러 핸들링)으로 비동기 처리를 할 수 있게 해줍니다.
Promise와 then, catch, finally 예시
function asyncTask(value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (value) {
resolve(`입력값: ${value}`);
} else {
reject('입력값이 없습니다.');
}
}, 1000);
});
}
asyncTask('Hello')
.then(result => {
// 성공적으로 처리된 경우
console.log('성공:', result);
})
.catch(error => {
// 오류 발생시 처리
console.error('오류:', error);
})
.finally(() => {
// 성공 또는 오류 후 마지막으로 실행
console.log('작업 완료');
});
// 실행 결과
// 성공: 입력값: Hello
// 작업 완료
then, catch, finally 메서드를 통해 비동기 작업의 성공, 실패, 완료를 처리합니다.
체인 형태로 비동기 작업을 연결할 수 있어 가독성이 높아집니다.
여전히 복잡한 비동기 로직에서는 then chain
이 길어질 수 있는 문제가 있습니다.
프로미스의 상태 (States)
대기 상태 (Pending): 프로미스가 결과가 정해지지 않은 초기 상태. 비동기 작업이 아직 완료되지 않았음을 나타냅니다.
이행 상태 (Fulfilled): 프로미스가 성공적으로 완료된 상태. resolve 함수가 호출되면 이 상태가 됩니다.
거부 상태 (Rejected): 프로미스가 실패한 상태. reject 함수가 호출되거나 예외가 발생했을 때 이 상태가 됩니다.
이 세 가지 상태는 프로미스 객체의 상태 변화를 나타내며, 프로미스를 생성하고 해결하는 과정을 설명합니다.
콜백보다 가독성이 좋고, 에러 처리가 용이합니다.
then 체인이 길어지고, then 지옥이 가능합니다. 복잡한 비동기 흐름을 관리하기 어려울 수 있습니다.
getData()
.then(result => {
return getMoreData(result)
.then(moreResult => {
return getEvenMoreData(moreResult)
.then(evenMoreResult => {
console.log(evenMoreResult); // 결과 처리
// 추가적인 then 중첩...
});
});
});
async/await은 비동기 작업을 동기적으로 보여주는 문법이다. async 함수는 항상 promise를 반환하며, await은 async 함수 내부에서만 사용할 수 있습니다.
등장 년도: 2017년, ECMAScript 2017(ES8)에 공식적으로 도입됨.
비동기 작업을 마치 동기 작업처럼 쉽고 명확하게 작성할 수 있게 해줍니다. 이는 프로미스를 기반으로 하지만, 보다 선언적이고 직관적인 코드를 작성할 수 있게 도와줍니다.
async 함수 내부에서 await 키워드를 사용하여 프로미스의 결과를 기다립니다.
동기 코드처럼 순차적이고 직관적인 코드 작성이 가능합니다.
try-catch와 결합하여 에러 처리를 할 수 있습니다.
코드의 흐름을 쉽게 이해할 수 있어 가독성과 유지 보수성이 좋아집니다.
// Promise를 반환하는 비동기 함수
function fetchData(url) {
return new Promise((resolve, reject) => {
// 비동기 작업(예: 네트워크 요청)
setTimeout(() => {
if (url === "validUrl") {
resolve("데이터 받기 성공");
} else {
reject("잘못된 URL");
}
}, 1000);
});
}
// async/await를 사용하여 비동기 함수 호출
async function getData() {
try {
const data = await fetchData("validUrl");
console.log(data); // '데이터 받기 성공' 출력
} catch (error) {
console.error("오류 발생:", error); // 오류 발생시 처리
}
}
getData();
try-catch 문은 실행할 코드 블록(try 블록)과 예외(Exception)가 발생했을 때 실행할 코드 블록(catch 블록)을 제공합니다.
등장 년도: 1999년, ECMAScript 3에서 도입.
'예외'는 프로그램의 광범위한 부분에서 발생할 수 있는 오류를 의미합니다.
주의할 점은 reject가 try-catch 안에 있어도 catch 넘어가지 않습니다.
오류가 발생했을 때 프로그램이 중단되는 것을 방지하고, 오류를 적절하게 처리할 수 있습니다.
프로그램 실행 중에 발생하는 오류를 처리하고, 오류로 인한 프로그램 중단을 방지하기 위함.
try {
// 오류가 발생할 수 있는 코드
let result = someFunctionThatMightThrow();
console.log(result);
} catch (error) {
// 오류 처리
console.error("An error occurred:", error.message);
}
throw 키워드를 사용하는 경우
개발자가 직접 오류 상황을 정의하고 예외를 발생시킵니다.
예외는 문자열, 숫자, 객체 또는 Error 객체의 인스턴스일 수 있습니다.
throw로 발생할 예외는 가장 가까운 try-catch 블록에 의해 잡힙니다.
try {
throw new Error("이것은 오류입니다!");
} catch (e) {
console.log(e.message); // "이것은 오류입니다!"를 출력
}
자동으로 발생하는 예외
JavaScript 엔진은 코드 실행 중에 오류를 발견하면 자동으로 예외를 발생시킵니다.
이러한 오류에는 타입 오류, 참조 오류, 범위 초과 오류 등이 포함됩니다.
이러한 자동 예외도 try-catch로 잡을 수 있습니다.
try {
var obj = null;
console.log(obj.toString()); // TypeError: Cannot read property 'toString' of null
} catch (e) {
console.log(e.message); // TypeError에 대한 메시지를 출력
}
따라서 try-catch는 명시적(throw)이든 자동이든 프로그램 실행 중 발생하는 예외를 잡아내어 오류 처리를 수행하는 데 사용합니다.
비동기 예외 처리
async-await를 사용하면, await이 지정된 프로미스가 reject될 때 발생하는 오류를 try-catch로 간편하게 잡을 수 있습니다.
동기적 작동
async-await는 비동기 작업을 동기적으로 처리하는 것처럼 만들어줍니다. 이 때문에, try-catch를 사용하여 async 함수 내부에서 발생하는 오류를 잡을 수 있습니다.
간소화된 오류 처리
이 접근법은 비동기 작업에서 발생할 수 있는 오류를 더욱 간단하고 직관적으로 처리할 수 있게 해줍니다.
async-await 없이는 reject된 프로미스를 try-catch로 잡을 수 없기 때문에, 코드가 더 복잡해질 수 있으며, 오류 관리를 위한 추가적인 노력이 필요합니다
이번에 비동기 처리에 관해 다시 공부하고 정리하면서, 전에 알고 있던 것보다 더 깊은 이해를 할 수 있었다. 새로운 관점과 흥미로운 측면들을 발견했고, 이 과정이 매우 재미있었다. 이번 학습을 통해 비동기 처리에 대한 내 지식과 이해가 한층 더 성장했다고 느꼈다.