동기와 비동기가 무슨 말이야?
JavaScript를 공부하다보면 ‘비동기’라는 단어가 자주 언급된다는 것을 발견할 수 있다.
자주 언급이 된다는 것은 그만큼 고려할만하고 필요하다는 말이기도하니 궁금하지 않을 수가 없다.
동기식(Synchronous)과 비동기식(Asynchronous)라는 개념은 프로그램 요소들이
작업을 요청(Request)하고 응답(Response)하는 과정에서 정의될 수 있는 개념이다.
그러니 서버와 관련된 작업을 할 때 비동기에 대한 언급을 자주 볼 수 있는 것이기도하다.
동기식 모델
동기식 모델은 요청과 응답의 과정에서 순차적으로 일을 처리해 나가는 방식을 의미한다.
융통성이 없다고 표현을 해도 될까…
하나를 처리하고 끝내야만 다음 요청을 수행한다. 현재 요청이 처리되기 전까진 무한정 대기다.
예컨대, A, B, C 세 사람이 있다고 가정하고
화장실 청소와 저녁 식사 요리를 요청했을 때,
화장실 청소를 다 마친 후 저녁 식사 요리를 시작할 것이다.
적어도 화장실 청소가 끝나기 전까진 저녁은 먹지 못한다.
여기서 한 걸음 더 나아가 화장실 청소만 살펴보자
A가 화장실 청소를 하다가 B와 C에게 각각 변기 청소와 바닥 청소를 부탁했다고 하자.
그러면 A는 B에게 변기 청소를 요청하고 B가 변기 청소가 끝나야
C에게 바닥 청소를 요청할 것이다.
뭔가 소란스러운 예시였던 것 같지만, 그래도 동기식 모델은 이런 방식으로 작동한다.
동기 모델의 장점은 실행 순서가 명확하고 예측 가능하다는 점이다.
적어도 B가 변기 청소 이후 어떤 일이 일어날지 알 수 있지 않은가.
예측 가능하다는 점은 현재 발생한 문제의 원인 파악에도 도움이 된다.
그러나 한편으론 속도가 치명적인 단점이 되기도 한다.
이는 작업이 오래 걸리는 경우 전체 프로그램의 실행 시간에도 영향을 주고
결국 전체 프로그램의 실행 시간이 느려지는 결과를 보여준다.
저런 식의 일 처리는 나의 저녁 식사를 결단코 보장해주지 못할 것이다.
비동기식 모델
비동기식 모델은 요청과 응답의 과정에서 동시에 처리해 나가는 방식을 의미한다.
집중력이 부족하다고 해야하나… 멀티테스커다
하나를 처리하는 도중이라도 다른 작업을 수행한다. 처리하던 작업은 백그라운드에서 마저 처리된다.
그러니 오히려 작업이 끝나기를 기다리지 않고 요청한 작업 결과를 원하는 시점에 확인(Polling)하거나
요청 결과를 통보 받는(Event Driven)을 설정한다.
아까의 예시처럼 A는 B에게 변기 청소를 요청한 상태로 C에게 바닥 청소를 요청한다.
A는 B의 변기 청소가 끝나는 것을 기다리지 않고 자신이 정해놓은 시간에 확인하러 가보거나
B가 변기 청소가 끝나면 A에게 말하러 온다.
비동기식 모델은 작업이 오래 걸리는 경우에도 다른 작업을 실행할 수 있다는 장점이 있다.
적어도 동기 모델보다 저녁 식사를 일찍 할 가능성이 더 열려있는 셈이다.
비동기 코드를 작성하면 병렬 작업을 하기 때문에 시스템 자원을 최대한 활용하여 처리한다.
이런 방식은 시스템의 응답성은 높이고, 단위 시간 당 더 많은 요청을 처리할 수 있게 된다.
그러나 한편으로는 실패한 작업이 있어도 다른 작업이 진행되기에 실행을 취소해주는 메커니즘이나
롤백을 위한 신호대기 같은 추가적인 고민이 필요하다는 점과 Race condition 방지를 해야한다는 것이다.
물론 당연하게도 코드가 굉장히 복잡해질 수 있다는 단점도 있다.
비동기식 모델은 주로 콜백함수나 프로미스, async/await 메커니즘을 사용해 작성하는 것이 특징이다.
프로미스
프로미스(promise)는 JavaScript에서 비동기 작업을 처리하기 위한 객체다.
비동기 모델의 성공, 실패 또는 진행 상태를 나타내는 역할을 한다.
프로미스는 3가지의 상태를 가질 수 있다.
첫 번째, 대기(pending) : 대기는 초기 상태로서 작업이 아직 완료되지 않은 상태를 나타낸다.
두 번째, 이행(fulfilled) : 이행은 작업이 성공적으로 완료된 상태를 나타낸다.
세 번째, 거부(rej) : 실패한 작업의 상태를 나타낸다.
연결(chaing) : 프로미스는 결과값을 프로미스 값으로 주기 때문에, 다른 프로미스와 연결해
비동기 작업을 순차적으로 실행할 수 있다. 이러면 작업 순서와 의존성이 명확해진다.
프로미스 예시
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행 설정
// resolve(value) = 작업 성공시 호출
// reject(err) = 작업 실패시 호출
if(condition){
resolve(value);
}
else{
reject(err);
}
});
myPromis
.then((res) => {
// 성공하면 실행되는 코드
})
.catch((err) => {
// 실패시 실행되는 코드
})
.finally(() => {
// 작업 완료 이후 언제나 실행되는 코드
});
async / await 메커니즘
async와 await은 JavaScript에서 비동기 코드를 작성하고 처리하는 방법이다.
async 함수는 비동기적인 동작을 하는 함수를 정의할 때 사용하는데,
항상 프로미스를 반환하고, 내부에 await을 작성하면 다른 작업을 기다릴 수 있다.
await은 async 내에서만 사용 가능한데, 프로미스가 이행될 때까지 코드 실행을 멈추고
프로미스 결과를 반환하는 것이다. 비동기 코드지만 동기적으로 동작할 수 있다는 특징이 있다.
async /await 예시
function functionName(){
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행 설정
// resolve(value) = 작업 성공시 호출
// reject(err) = 작업 실패시 호출
if(condition){
resolve(value);
}
else{
reject(err);
}
});
async myPromiseAsync(){
try{
const data = await functionName(); // functionName 작업완료까지 대기
// 대기 이후 추가 작업 수행
} catch (err) {
// 실패시 실행되는 코드
} finally{
// 작업 완료 이후 언제나 실행되는 코드
}
}
}
프로미스와 async / await의 차이점
프로미스는 비동기 작업을 처리하기 위해 then catch 같은 메소드 체어닝을 사용한다
async/await은 비동기 작업을 동지적으로 작성하기도해 코드 가독성과 이해도를 향상시킨다는 차이점이 있다.
프로미스는 모든 작업이 완료될 때까지 대기하고 병렬 처리하지만, async/await은 단일 작업에 집중해
병렬처리를 하려면 별도로 설정해줘야만 가능하다
또한, 프로미스는 항상 프로미스를 반환하지만 async/await은 실제 값을 반환한다.
분명, 둘다 비동기 코드를 처리하는 메커니즘이지만
async/await은 프로미스를 기반으로 구현돼 보다 편리한 문법을 제공한다는 차이가 있다.