2023-02-23
매우 중요한 동기, 비동기 프로그래밍
동기, 비동기에 이해와 이벤트 루프, 태스크 큐에 대해 자세히 알아보자!
동기
현재 작업이 수행 완료될 때까지 다음 작업들이 대기하는 방식을 동기 처리라고 한다.
즉, 작업들을 순차적으로 실행하면서 각 작업이 완료될 때까지 대기하는 방식이다.
이로인해 작업들이 순서대로 실행되는 것을 보장한다.
비동기
현재 작업이 완료되지 않아도 다음 작업을 수행하는 방식을 비동기 처리라고 하며,
비동기 처리를 사용하면 작업들이 독립적으로 실행되므로 하나의 작업이 끝날 때까지 다른 작업들이 기다릴 필요가 없다.
이를 통해 블로킹 현상이 발생하지 않고, 작업들이 병렬적으로 실행될 수 있다.
이벤트 루프
비동기 처리를 담당하는 메커니즘으로 JS 엔진이 아닌 브라우저에 내장된 기능이다.
이벤트 루프의 주요 역할은 비동기 작업들을 관리하고 실행 순서를 조정한다.
동작 원리
이벤트 루프는 호출 스택(call stack)과 태스크 큐들을 계속 확인하면서,
호출 스택이 비어있을 경우 태스크 큐에 대기 중이던 비동기 작업들을 호출 스택으로 이동시켜 실행한다.
태스크 큐
마이크로 태스크 큐(micro task queue)
마이크로 태스크 큐는 매크로 태스크 큐보다 우선 순위가 높으며,
비동기 API 호출과 같이 빨리 수행해야 하는 작업에 사용된다.
예로 Promise
, async/await
.
매크로 태스크 큐(macro task queue or task queue)
매크로 테스크 큐는 마이크로 테스크 큐 보다 우선 순위가 낮으며,
더 크고 느리며 시간이 덜 중요한 작업에 사용된다.
예로 DOM 이벤트
, setTimeOut
, setInterval
.
동작 원리
위의 코드를 실행했을 때 어떠한 결과가 도출될까? 정답은 start -> end -> promise -> setTime 순서다.
그 이유는 아래 내용들을 읽으면 이해할 수 있다.
비동기 함수
비동기 함수란 함수 내부에 비동기 함수를 포함한 함수를 의미한다.
비동기 함수를 호출하면 내부 비동기로 동작하는 코드가 완료되지 않았다 해도 기다리지 않고 종료된다.
즉, 비동기 코드는 비동기 함수가 종료된 이후에 완료된다.
위의 코드를 보면 setTimeOut
은 비동기 함수다. 타이머가 만료되면 콜백 함수를 스케줄링하고(태스크 큐로 이동),
콜 스택에서 팝되어 제거된다. 스케줄링된 콜백 함수는 호출 스택이 비었을 경우 푸시되어 실행되기 떄문에,
로그로 찍은 식별자 값은 재할당되지 않은채 출력되고 종료된다.
이와 같은 동작으로 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프 식별자에도 할당할 수 없다.
콜백 패턴
따라서 비동기 처리 결과에 대한 후속 처리는 비동기 함수 내부
에서 수행해야 한다.
일반적으로 이러한 후속 처리를 아래와 같이 콜백 함수
로 처리한다.
하지만 이런 콜백 함수가 또 다른 비동기를 호출하고 이것이 연쇄적으로 발생한다면,
복잡도가 높아지는 현상이 발생하고 이를 콜백 헬
이라 한다.
콜백 패턴에 에러 처리 한계
콜백 패턴의 문제점 중 하나가 에러 처리가 곤란하다는 것이다.
아래 코드를 보면 try
에서 발생하는 에러를 캐치하지 못한다.
그 이유는 에러는 호출자 방향으로 전파되는데 setTimeout은 에러의 호출자가 아니기 때문이다.
즉, setTimeout은 타이머가 만료되면 콜백 함수를 스케줄링하고 호출 스택에서 사라지기 때문이다.
따라서 호출자의 호출 스택과는 별개로 실행되어, 내부적으로 처리되지 않은 에러로 남게 된다.
이러한 한계를 해결하기 위해 ES6에 Promise
객체가 등장했다.
Promise
비동기 처리 상태
와 처리 결과
를 관리하는 빌트인 객체다.
비동기 처리를 수행할 콜백 함수 resolve
, reject
를 인수로 전달 받는다.
상태는 pending
, fulfulled
, rejected
로 수행되지 않은 상태, 수행된 상태, 실패한 상태로 구분된다.
처리 상태가 변화하면 후속 처리 메서드 then
, catch
, finally
로 처리 결과가 인수로 전달된다.
자세한 사용법은 MDN - Using promises에서 확인!
Promise Chaining
후속 처리 메서드는 언제나 Promise
객체를 반환하므로 연속적
으로 호출할 수 있는 현상을 말한다.
이러한 패턴은 가독성이 좋지 않아 ES8의 async/await
를 통해 해결한다.
async/await
프로미스의 후속 처리 메서드를 사용할 필요 없이, 동기 처리와 같이 처리 결과를 반환하도록 구현할 수 있다.
아래 코드를 보면 await 키워드를 사용해 settled 상태 즉, 비동기 처리가 수행 완료된 상태가
될 때까지 대기하다 처리가 완료되면 결과를 반환한다.
참조
모던 자바스크립트 Deep Dive - 위키북스
DEV - javascript-visualized-promises-async-await
MDN - Using promises