그래서 하나의 일을 할 때 하나밖에 못하는데 그 하나가 오래 걸리는 일이면, 다른 작업들은 그 하나의 일이 끝날때 까지 기다려야 한다.
→ 이러한 문제점을 해결하기 위해서 비동기로 작업을 수행한다.
비동기 요청이 여러 개 있고 하나의 요청이 다른 요청의 결과에 의존할 때, 아래와 같은 몇 가지 방법을 사용할 수 있다.
firstAsyncFunction(param1, function(result1) {
secondAsyncFunction(result1, function(result2) {
thirdAsyncFunction(result2, function(result3) {
// ...
});
});
});
firstAsyncFunction(param1)
.then(result1 => secondAsyncFunction(result1))
.then(result2 => thirdAsyncFunction(result2))
.then(result3 => {
// ...
})
.catch(error => {
// 에러 처리
});
async function runAsyncTasks() {
try {
const result1 = await firstAsyncFunction(param1);
const result2 = await secondAsyncFunction(result1);
const result3 = await thirdAsyncFunction(result2);
// ...
} catch (error) {
// 에러 처리
}
}
Promise.all([firstAsyncFunction(param1), secondAsyncFunction(param2)])
.then(([result1, result2]) => {
// ...
})
.catch(error => {
// 에러 처리
});
여기서 중요한 것은 선택한 방법이 애플리케이션의 요구 사항과 잘 맞아야 한다는 것이다.
콜백은 자바스크립트에서 비동기 처리를 다루는 가장 기본적인 방법 중 하나이다. 함수를 파라미터로 다른 함수에 전달하고, 특정 이벤트가 발생하거나 작업이 완료되면 해당 함수를 호출하는 방식이다.
function doSomething(callback) {
// 작업을 수행
callback('작업 결과');
}
doSomething(function(result) {
console.log('콜백 실행:', result);
});
콜백의 문제점
코드의 구성 요소
function doSomething(callback) {
// 작업을 수행
callback('작업 결과');
}
doSomething(function(result) {
console.log('콜백 실행:', result);
});
코드의 실행 흐름은 다음과 같다
이렇게 콜백 함수를 사용하면 함수의 작업이 완료된 후에 어떤 작업을 할 것인지를 유연하게 설정할 수 있다.
function firstFunction(parameters, callback) {
//do something
const response1 = request(`http://abc.com?id=${parameters.id}`);
callback(response1);
}
function secondFunction(response1, callback) {
const response2 = request('http://bcd.com', response1);
callback()
}
firstFunction(para, function(response1) {
secondFunction(response1, function() {
thirdFunction(para, function() {
//...
})
})
})
해당 코드는 콜백 패턴을 사용하여 비동기 작업을 처리하는 예시이다. 각 함수(firstFunction, secondFunction, thirdFunction)는 어떤 작업을 수행한 후에 콜백 함수를 호출한다. 여기서는 간단한 설명을 위해 request 함수를 사용하고 있다고 가정하겠습니다.
함수 설명
콜백 중첩 (Callback Hell)
코드의 마지막 부분에서 firstFunction을 호출하고, 그 콜백 내에서 secondFunction을 호출하고 있습니다. 이런 패턴을 콜백 중첩이라고 하며, "콜백 지옥(Callback Hell)"으로도 알려져 있다. 이는 코드가 복잡해지고 유지보수가 어려워질 수 있는 문제점을 가지고 있다.
주의 사항
secondFunction에서 callback을 호출할 때 인자가 없다. 이는 실제 코드에서 문제가 될 수 있다.
프로미스는 비동기 작업을 더 쉽게 처리할 수 있는 객체이다. 프로미스는 'pending', 'fulfilled', 'rejected' 등 세 가지 상태를 가진다.
Promise 객체는 new 키워드와 생성자를 사용햐 만든다. 생성자는 매개변수로 "실행 함수"를 받는다. 이 함수는 매개 변수로 두 가지 함수를 받아야 하는데, 첫 번째 함수(resolve)는 비동기 작업을 성공적으로 완료해 결과를 값으로 반환할 때 호출해야 하고, 두 번째 함수(reject)는 작업이 실패하여 오류의 원인을 반환할 때 호출하면 된다. 두 번째 함수는 주로 오류 객체를 받는다.
//예시 코드
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue) // fulfilled
// or
// reject("failure reason") // rejected
});
const myPromise = new Promise((resolve, reject) => {
if (/* 조건 */) {
resolve('성공');
} else {
reject('실패');
}
});
myPromise
.then(result => {
console.log('결과:', result);
})
.catch(error => {
console.log('에러:', error);
});
프로미스의 장점
코드의 구성 요소
const myPromise = new Promise((resolve, reject) => {
if (/* 조건 */) {
resolve('성공');
} else {
reject('실패');
}
});
myPromise
.then(result => {
console.log('결과:', result);
})
.catch(error => {
console.log('에러:', error);
});
코드의 실행 흐름은 다음과 같다.
(/* 조건 */)
을 검사합니다. 이 부분은 코드에 구현되어 있지 않아서 실제 조건을 확인할 수 없다.이렇게 Promise를 사용하면 비동기 작업의 성공과 실패를 더 명확하고 가독성 좋게 처리할 수 있다.
function fetchData() {
return new Promise(function(resolve, reject) {
// 비동기 요청, 성공했다고 가정하므로 const success= true; 를 선언
const success = true;
if (success) {
resolve('성공')
} else {
reject('실패')
}
});
}
fetchData()
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error);
})
해당 코드는 JavaScript의 Promise를 사용하여 비동기 작업을 처리하는 예시이다. 코드는 다음과 같은 방식으로 작동한다.
fetchData
함수
.then()
와 .catch()
이러한 방식을 통해 비동기 작업을 더욱 쉽게 관리할 수 있다. 이 코드에서는 실제 비동기 작업을 수행하지는 않지만, 이러한 패턴은 실제 HTTP 요청, 파일 읽기 등 다양한 비동기 작업에 적용할 수 있다.
fetch('https://jsonplaceholder.typicode.com/todos/1') // 데이터 받아옴
.then(response1 => response1.json()) // json형식으로 변환
.then(json => console.log(json)) // console.log로 출력
.then(() => fetch('https://jsonplaceholder.typicode.com/todos/2')) //데이터 받아옴
.then(response2 => response2.json()) // json형식으로 변환
.then(json2 => console.log(json2)) // console.log로 출력
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log('작업 끝!')
})
Promise.all() 메서드는 여러 개의 Promise 객체를 포함하는 배열을 입력으로 받아, 모든 Promise가 성공적으로 완료될 때까지 대기한 후에, 각각의 Promise 결과를 배열로 반환한다. 만약 하나라도 실패하면 Promise.all()도 실패하며, 첫 번째로 발생한 에러를 반환한다.
기본적인 사용 예시는 다음과 같다.
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // 출력: [1, 2, 3]
})
.catch((error) => {
console.log(`Error: ${error}`);
});
이 메서드는 각각의 Promise가 서로에게 의존적이지 않고 독립적으로 실행될 수 있을 때 유용하게 사용된다. 예를 들어, 여러 API에서 데이터를 동시에 가져와야 할 때 유용하게 사용할 수 있다.
const fetch1 = fetch('https://api.example.com/data1');
const fetch2 = fetch('https://api.example.com/data2');
const fetch3 = fetch('https://api.example.com/data3');
Promise.all([fetch1, fetch2, fetch3])
.then((responses) => {
// responses 배열에서 각 response 객체를 처리
})
.catch((error) => {
console.log(`Error: ${error}`);
});
이렇게 Promise.all()을 사용하면 모든 비동기 작업이 완료될 때까지 대기한 다음에 결과를 한 번에 처리할 수 있다.
Promise.race()는 여러 개의 Promise 객체를 포함하는 배열을 입력으로 받아, 가장 먼저 완료되는 (즉, "이긴") Promise의 결과나 에러를 반환한다. 다시 말해, 여러 개의 Promise 중에서 가장 먼저 성공하거나 실패하는 Promise의 상태를 그대로 따른다.
이 메서드는 다양한 용도로 활용될 수 있다. 예를 들어, 여러 개의 비동기 작업 중 먼저 끝나는 작업만을 처리하고자 할 때, 또는 타임아웃을 설정하고자 할 때 유용하게 사용된다.
기본적인 사용 예시는 다음과 같다.
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // 출력: 'two'
})
.catch((error) => {
console.log(`Error: ${error}`);
});
이 예시에서 promise2가 더 빨리 완료되므로, Promise.race()의 결과는 'two'이다.
Promise.race()는 시간 제한이 있는 작업에 유용하게 사용할 수 있다. 예를 들어, 특정 시간 내에 작업이 완료되지 않으면 자동으로 실패로 처리하고자 할 때 다음과 같이 작성할 수 있다.
const fetchWithTimeout = (url, ms) => {
const timeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Request timed out'));
}, ms);
});
const fetchRequest = fetch(url);
return Promise.race([timeout, fetchRequest]);
};
fetchWithTimeout('https://api.example.com/data', 5000)
.then((response) => {
// 데이터 처리
})
.catch((error) => {
console.log(`Error: ${error}`);
});
이 예시에서 fetchWithTimeout 함수는 두 개의 Promise 중 먼저 완료되는 것을 반환한다. 하나는 fetch 요청, 다른 하나는 지정된 시간(ms) 후에 자동으로 실패하는 timeout Promise입니다. 이를 통해 요청에 시간 제한을 둘 수 있다.
자바스크립트는 이 두 가지를 넘어 async/await를 통해 더 직관적인 비동기 처리를 가능하게 하고 있다.
async와 await는 자바스크립트에서 비동기 작업을 더 쉽고 가독성 있게 처리하기 위한 문법이다. 이들은 ES2017(ES8)에서 도입되었으며, 기존의 콜백 함수나 프로미스의 사용을 단순화하고 코드를 더 읽기 쉽게 만들어 준다.
async 키워드는 함수 앞에 사용되며, 이 함수는 항상 Promise를 반환한다. 만약 async 함수가 값을 반환하면, 이 값은 .then() 메서드를 통해 접근할 수 있는 프로미스로 감싸진다. 만약 함수에서 예외가 발생하면, 이는 .catch() 메서드를 통해 잡을 수 있는 프로미스로 반환된다.
async function foo() {
return 'Hello';
}
foo().then(result => console.log(result)); // 출력: "Hello"
await 키워드는 async 함수 내부에서만 사용할 수 있으며, Promise의 완료를 기다린 후 결과값을 반환한다. await을 사용하면 비동기 코드를 마치 동기 코드처럼 작성할 수 있어 가독성이 향상된다.
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}
async function fetchAllData() {
const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
// ...do something with data1 and data2
}
이런 식으로, async/await는 비동기 프로그래밍을 단순하고 가독성 있게 만들어 준다.
fetch('https://jsonplaceholder.typicode.com/todos/1') // 데이터 받아옴
.then(response1 => response1.json()) // json형식으로 변환
.then(json => console.log(json)) // console.log로 출력
.then(() => fetch('https://jsonplaceholder.typicode.com/todos/2')) //데이터 받아옴
.then(response2 => response2.json()) // json형식으로 변환
.then(json2 => console.log(json2)) // console.log로 출력
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log('작업 끝!')
})
위 코드를 아래로 변환이 가능하다.
async function makeRequests() {
try {
const response1 = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const jsonResponse1 = await response1.json();
console.log('jsonResponse1', jsonResponse1);
const response2 = await fetch('https://jsonplaceholder.typicode.com/todos/2');
const jsonResponse2 = await response2.json();
console.log('jsonResponse2', jsonResponse2);
} catch(error) {
console.log(error)
} finally {
console.log('작업 끝!')