동기(Synchronous)와 비동기(Asynchronous)
01. 개요
- 동기 처리
동기 처리란, 작업을 순차적으로 처리하는 것을 말한다.
자바스크립트 코드는 기본적으로 동기적으로 처리된다.
동기적으로 처리되는 코드는 위에서부터 아래로 순차적으로 실행되며, 어떤 작업이 끝나야 다음 작업을 수행할 수 있다.
하지만, 동기적으로 처리되는 코드는 작업이 끝날 때까지 다른 작업을 수행할 수 없다는 단점이 있다.
- 은행에서 번호 순서대로 업무를 처리하는 것, 순차적으로 처리되는 것
console.log('은행 1번 번호표 업무 시작');
console.log('은행 1번 번호표 업무 끝');
console.log('은행 2번 번호표 업무 시작');
console.log('은행 2번 번호표 업무 끝');
- 비동기 처리
비동기 처리란, 작업을 동시에 처리하는 것을 말한다.
비동기 처리는 이러한 단점을 보완하기 위해 등장했다.
비동기 처리는 작업을 동시에 처리할 수 있으며, 작업이 끝나지 않아도 다른 작업을 수행할 수 있다.
- 비동기처리의 예: 커피 주문 시 앞번호가 먼저 나오는 것, 동시에 처리되는 것
setTimeout(() => {
console.log('느린 미니언즈 라떼');
}, 1000);
console.log('빠른 아메리카노');
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
.then(data => console.log(data));
console.log('데이터 요청 중...');
- fetch API를 사용하여 서버에서 데이터를 가져올 수 있습니다.
- fetch 함수는 비동기 방식으로 서버에 데이터를 요청하고,
- 요청이 완료되면 then 메서드를 통해 데이터를 처리합니다.
- 따라서 '데이터 요청 중...'이 먼저 출력되고, 서버 응답이 완료되면 데이터가 출력됩니다.
- 비동기 처리의 장점
- 효율성: 비동기 처리는 결과를 기다리지 않고 다른 작업을 수행할 수 있기 때문에 시스템 자원을 효율적으로 활용할 수 있습니다.
- 응답성: 사용자 경험을 향상시키는 데 유용하며, UI가 멈추지 않고 반응을 계속 유지할 수 있습니다.
- 병렬 처리: 여러 작업을 병렬로 처리함으로써 전체적인 처리 속도를 높일 수 있습니다.
- 비동기 처리의 실무 예시
- Ajax (Asynchronous JavaScript and XML): 웹 애플리케이션에서 페이지를 전체적으로 새로 고침하지 않고 서버와 통신할 수 있게 합니다.
- Promise 및 async/await: JavaScript에서 비동기 작업을 다루기 위한 문법으로, 비동기 코드의 가독성을 높입니다.
- Node.js의 비동기 I/O: Node.js는 비동기 방식으로 파일 시스템, 데이터베이스, 네트워크 요청 등을 처리하여 높은 성능을 제공합니다.
- 비동기 API 호출: 클라이언트 애플리케이션에서 서버 API를 호출할 때, 비동기 요청을 통해 다른 작업을 차단하지 않고 처리가 가능합니다.
- 실시간 데이터 처리: 채팅 애플리케이션, 실시간 알림 시스템 등에서 비동기 처리를 통해 즉각적인 데이터 업데이트가 가능합니다.
이 중 가장 많이 사용되는 방식은 Promise 및 async/await입니다. 이에 대해 자세히 알아보겠습니다.
02. 콜백(Callback) 패턴
- 콜백(Callback): 비동기 처리를 위해 다른 함수에 인자로 넘겨주는 함수로, 비동기 작업이 완료되면 콜백 함수가 실행됩니다.
const a = () => console.log("a");
const b = () => console.log("b");
a(); // a
b(); // b
- 위 코드는 동기적으로 실행되기 때문에 a 함수가 먼저 실행되고, b 함수가 실행됩니다.
- 숫자 2가 먼저 출력되게 하려면 어떻게 해야 할까요?
// 비동기 처리를 사용하기 위해 setTimeout 함수를 사용합니다.
// a 함수가 실행되고 1초 후에 b 함수가 실행됩니다.
const a = () => {
setTimeout(() => {
console.log("a");
}, 1000);
};
const b = () => console.log("b");
a(); // a
b(); // b
- 위 코드는 비동기적으로 실행되기 때문에 a 함수가 실행되고, 1초 후에 1이 출력됩니다.
- 이처럼 콜백 함수를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.
- 만약 출력하는 숫자 1과 2를 순차적으로 출력하려면 어떻게 해야 할까요?
// 콜백 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.
const a = callback => {
setTimeout(() => {
console.log("a"); // a
callback(); // b
}, 1000);
};
const b = () => console.log("b");
a(b); // a, b
- 위 코드는 a 함수가 실행되고, 1이 출력된 후에 콜백 함수가 실행되어 b 함수가 실행됩니다.
- 이처럼 콜백 함수를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.
03. Promise 패턴
new Promise((resolve, reject) => { ... });
- Promise: 비동기 작업이 완료되었을 때 결과를 반환하거나 에러를 처리할 수 있는 객체로, 비동기 작업을 보다 효율적으로 처리할 수 있습니다. Promise 객체를 사용하면 콜백 함수의 중첩 문제(콜백 지옥)를 해결할 수 있으며, 비동기 코드의 가독성을 높여줍니다.
프로미스는 세 가지 상태를 가진다.
- 대기(pending) : 비동기 처리가 아직 수행되지 않은 상태
- 이행(fulfilled) : 비동기 처리가 성공적으로 수행된 상태
- 거부(rejected) : 비동기 처리가 실패한 상태
프로미스를 사용하여 비동기 처리를 하려면, new Promise()를 사용하여 프로미스 객체를 생성하고, resolve와 reject를 사용하여 프로미스의 상태를 변경한다.
// Promise 객체 생성
// new Promise((resolve, reject) => {...})를 통해 새로운 프로미스 객체 생성
// 이 객체는 비동기 작업을 수행할 콜백 함수를 인자로 받으며,
// 이 콜백 함수는 resolve와 reject라는 두 개의 함수를 매개변수로 가집니다.
// resolve, reject 함수는 프로미스의 상태를 변경하는 역할을 합니다.
const a = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 1초 후에
console.log(1);
resolve(); // 성공 시 호출
}, 1000);
});
};
const b = () => console.log(2);
// 아래 코드는 역시나 콜백 지옥에 빠집니다.
// then 메서드를 사용하여 비동기 작업을 순차적으로 처리할 수 있습니다.
// a 함수가 실행되고 완료된 후에 b 함수가 실행되고, c 함수가 실행됩니다.
a().then(() => {
b()
});
위 코드에서는 a
, b
, c
, d
함수가 각각 1초씩 기다린 후 콘솔에 숫자를 출력하는 비동기 작업을 수행합니다. 각 함수는 Promise
객체를 반환하며, 이 객체는 작업이 성공했을 때 resolve
를 호출합니다.
- 콜백지옥 해결
// b 함수 호출 시 return 키워드로 반환하게 되면
// promise 객체를 반환하게 되어 then 메서드를 연달아 사용할 수 있습니다.
a()
.then(() => {
return b();
})
위 코드와 같이 각 then 메서드 내에서 다음 비동기 함수를 반환하도록 변경하여, then 체인을 형성합니다. 이렇게 하면 콜백 지옥을 피할 수 있습니다.
- 화살표 축약
a()
.then(() => b())
화살표 함수의 축약 형태로, 함수 내부가 한 줄로 표현될 때 중괄호를 생략할 수 있습니다.
- 단축 표현
// resolve는 하나의 함수 데이터를 받아서 return으로 처리할 수 있습니다.
// 해서 b, c, d 함수 데이터 자체로 전달할 수 있습니다.
a()
.then(b)
.then(() => {
console.log('done');
});
- Promise.all
// Promise.all 메서드를 사용하여 여러 개의 비동기 작업을 동시에 처리할 수 있습니다.
Promise.all([a(), b()]).then(() => {
console.log('모든 작업이 완료되었습니다.');
});
- Promise를 활용한 실무 예시
JSONPlaceholder에서 제공하는 API를 사용하여 사용자 정보를 가져와 출력해보겠습니다.
fetch(): 네트워크 요청을 보내는 API로, Promise 객체를 반환하여 비동기 작업을 처리합니다.
Resolve: Promise 객체가 성공적으로 처리되었을 때 호출되는 콜백 함수로, 비동기 작업이 완료되면 결과를 반환합니다.
// userId 매개변수를 받아 사용자 정보를 가져오는 함수를 구현합니다.
const getUser = userId => {
// Promise 객체를 반환합니다. resolve 함수를 사용하여 비동기 작업이 완료되면 결과를 반환합니다.
return new Promise(resolve => {
// fetch 함수를 사용하여 JSONPlaceholder API를 호출합니다.
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
// fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.
// json 메서드를 사용하여 response 객체를 JSON 형태로 변환합니다.
.then(res => res.json())
// response 객체가 성공적으로 변환되면 data 객체를 반환합니다.
.then(data => resolve(data));
});
};
// 사용자 데이터를 가져오는 함수를 호출합니다.
getUser(1)
// getUser 함수가 성공적으로 호출되면 data 객체를 반환합니다.
.then(data => {
console.log(`사용자 ID 1의 이름은 ${data.name} 입니다.`);
return getUser(2);
})
.then(data => {
console.log(`사용자 ID 2의 이름은 ${data.name} 입니다.`);
return getUser(3);
})
.then(data => {
console.log(`사용자 ID 3의 이름은 ${data.name} 입니다.`);
});
- 위 코드는 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.
- getUser 함수는 Promise 객체를 반환하며, 사용자 정보를 가져온 후에 다음 사용자 정보를 가져옵니다.
- 이처럼 Promise를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.
04. async/await 패턴
- async/await: Promise를 더욱 간결하게 사용할 수 있는 문법으로, 비동기 작업을 동기적으로 처리할 수 있습니다.
// async 함수를 사용하여 비동기 작업을 처리합니다.
const a = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log(1);
resolve();
}, 1000);
});
};
// awite 함수를 사용하여 a함수를 실행하고 완료된 후에 b함수를 실행합니다.
// await 키워드는 뒷 부분의 비동기 작업이 완료될 때까지 기다립니다.
// await a();
// b();
// async 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.
// await 키워드를 사용할 때는 async 함수 내부에서만 사용할 수 있기 때문에
// main 함수를 async 함수로 선언합니다.
const main = async () => {
await a(); // 1
b(); // 2
};
main(); // 1, 2
- 위 코드는 async 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.
- main 함수는 async 함수로 선언되어 있으며, await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.
- async/await를 활용한 실무 예시
- 위 사용자 정보 API 예시를 async/await를 사용하여 구현해보겠습니다.
const getUser = userId => {
return new Promise(resolve => {
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(res => res.json())
.then(data => resolve(data));
});
};
// await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.
// const user1 = await getUser(1);
// console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);
// const user2 = await getUser(2);
// console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);
// const user3 = await getUser(3);
// console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
// await 키워드를 사용할 때는 async 함수로 묶어주어야 합니다.
const main = async () => {
const user1 = await getUser(1);
console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);
const user2 = await getUser(2);
console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);
const user3 = await getUser(3);
console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
};
main();
- 위 코드는 async/await를 사용하여 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.
- main 함수는 async 함수로 선언되어 있으며, await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.
05. Resolve, Reject, Error Handling
- Resolve: Promise 객체가 성공적으로 처리되었을 때 호출되는 콜백 함수로, 비동기 작업이 완료되면 결과를 반환합니다.
- Reject: Promise 객체가 실패했을 때 호출되는 콜백 함수로, 비동기 작업이 실패하면 에러를 반환합니다.
- Error Handling: Promise 객체가 실패했을 때 에러를 처리하는 방법으로, catch 메서드를 사용하여 에러를 처리합니다.
// 사용자 데이터를 가져오는 함수
const getUser = userId => {
// resolve, reject 함수를 사용하여 비동기 작업이 성공 또는 실패했을 때 결과를 반환합니다.
return new Promise((resolve, reject) => {
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => {
// fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.
// response 객체가 정상적이지 않을 경우 에러를 반환합니다.
if (!response.ok) {
throw new Error('네트워크 응답이 정상적이지 않습니다: ' + response.statusText);
}
return response.json();
})
.then(data => resolve(data))
// fetch 함수가 실패했을 경우 에러를 반환합니다.
.catch(error => reject(error));
});
};
// 사용자 데이터를 가져오는 함수를 호출합니다.
getUser(1)
.then(data => {
console.log(`사용자 ID 1의 이름은 ${data.name} 입니다.`);
return getUser(2);
})
.then(data => {
console.log(`사용자 ID 2의 이름은 ${data.name} 입니다.`);
return getUser(3);
})
.then(data => {
console.log(`사용자 ID 3의 이름은 ${data.name} 입니다.`);
})
// catch 메서드를 사용하여 에러를 처리합니다.
.catch(error => {
console.error('사용자 정보를 가져오는 중 오류가 발생했습니다.');
})
// finally 메서드를 사용하여 작업이 완료되었을 때 메시지를 출력합니다.
.finally(() => {
console.log('사용자 정보를 가져오는 작업이 완료되었습니다.');
});
// async/await를 사용하여 에러를 처리합니다.
const main = async () => {
try {
const user1 = await getUser(1);
console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);
const user2 = await getUser(2);
console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);
const user3 = await getUser(3);
console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
} catch (error) {
console.error('사용자 정보를 가져오는 중 오류가 발생했습니다.');
} finally {
console.log('사용자 정보를 가져오는 작업이 완료되었습니다.');
}
};
- 위 코드는 async/await를 사용하여 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.
- getWeather 함수는 Promise 객체를 반환하며, fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.
- response 객체가 정상적이지 않을 경우 에러를 반환하며, fetch 함수가 실패했을 경우 에러를 반환합니다.
- catch 메서드를 사용하여 에러를 처리하며, finally 메서드를 사용하여 작업이 완료되었을 때 메시지를 출력합니다.
06. axios
- axios: HTTP 클라이언트 라이브러리로, 비동기 방식으로 서버와 데이터를 주고받을 수 있습니다.
- 문법
const axios = require('axios');
// 지정된 ID를 가진 유저에 대한 요청
axios.get('/user?ID=12345')
.then(function (response) {
// 성공 핸들링
console.log(response);
})
.catch(function (error) {
// 에러 핸들링
console.log(error);
})
.finally(function () {
// 항상 실행되는 영역
});
- axios를 활용한 실무 예시
cdn : <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
const getUser = userId => {
return new Promise((resolve, reject) => {
axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => resolve(response.data))
.catch(error => reject(error));
});
};
// async/await를 사용하여 에러를 처리
const main = async () => {
try {
const user1 = await getUser(1);
console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);
const user2 = await getUser(2);
console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);
const user3 = await getUser(3);
console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
} catch (error) {
console.error('사용자 정보를 가져오는 중 오류가 발생했습니다.');
} finally {
console.log('사용자 정보를 가져오는 작업이 완료되었습니다.');
}
};
주요 변경사항:
fetch → axios.get
으로 변경response.json()
과정이 필요 없음 (axios는 자동으로 JSON 변환)response.ok
체크가 필요 없음 (axios는 에러 상태코드를 자동으로 catch로 보냄)response.data
로 바로 데이터에 접근 가능
axios가 더 간단하고 편리하죠! 😊
06. fetch API
- fetch(주소, 옵션)
- 네트워크를 통해 리소스를 요청(Request) 및 응답(Response)하는 API로,
- Promise 객체를 반환하여 비동기 작업을 처리합니다.
console 창에서 fetch 함수를 사용하여 JSONPlaceholder API를 호출해보겠습니다.
// fetch 함수를 사용하여 JSONPlaceholder API를 호출합니다.
console.log(fetch('https://jsonplaceholder.typicode.com/users'));
콘솔창의 promise instance를 클릭하면
then, catch, finally 메서드를 사용할 수 있도록 지정된 것을 확인할 수 있습니다.
fetch 함수를 사용하여 JSONPlaceholder API를 호출하면, Promise 객체를 반환합니다.
fetch('https://jsonplaceholder.typicode.com/users').then(res => console.log(res));
json 메서드를 사용하여 response 객체를 JSON 형태로 변환할 수 있습니다.
fetch('https://jsonplaceholder.typicode.com/users').then(res => console.log(res.json()));
그러면 json 형태로 변환된 데이터를 확인할 수 있습니다.
fetch 함수를 사용하여 JSONPlaceholder API를 호출하면, JSON 형태로 변환된 데이터를 반환합니다.
fetch('https://jsonplaceholder.typicode.com/users', {
// HTTP 요청 메서드를 지정합니다.
method: 'POST',
// 서버로 전송되는 데이터의 형식을 지정합니다.
headers: {
'Content-Type': 'application/json',
},
// 서버로 전송되는 데이터를 지정합니다.
// body 옵션에 명시하는 데이터는 문자열 형태로 지정해야 합니다.
// JSON.stringify 메서드를 사용하여 JSON 형태로 변환합니다.
body: JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1,
}),
})
.then(res => res.json()) // JSON 형태로 변환된 데이터를 반환합니다.
.then(json => console.log(json)); // JSON 형태로 변환된 데이터를 출력합니다.
// .then(res => {
// return res.json();
// });
fetch 함수가 promise 객체를 반환하므로, async/await를 사용하여 비동기 작업을 처리할 수 있습니다.
const main = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const json = await res.json();
console.log(json);
};
main();
'Front > JavaScript' 카테고리의 다른 글
이벤트(Events) - javascript 기본 (0) | 2024.07.06 |
---|---|
DOM - javascript 기본 (0) | 2024.07.06 |
변수(Variable) - javascript 기본 (0) | 2024.06.23 |
javascript 핵심 요약 (1) | 2024.05.30 |
비동기 (Async) - 뉴스 API 데이터 가져오기 예제 (1) | 2024.02.29 |