비동기 프로그래밍과 프로미스, async/await

정민교·2023년 7월 17일
0

javascript

목록 보기
9/16

📒 동기와 비동기 처리, 싱글스레드

자바스크립트에서 함수가 실행되려면 '함수 코드 평과 과정'에서 생서된 함수 실행 컨텍스트가 콜 스택에 푸시되어야 한다.

자바스크립트 엔진은 단 하나의 콜 스택을 갖는다. 따라서 함수를 실행할 수 있는 창구가 단 하나이고, 동시에 두 개 이상의 함수를 실행할 수 없다.

콜 스택에 쌓인 모든 실행 컨텍스트(현재 실행중인 실행 컨텍스트를 제외하고)는 모두 실행 대기중인 태스크(task)들이다.

자바스크립트 엔진은 한 번에 하나의 태스크만 실행할 수 있는 싱글 스레드 방식으로 동작한다.

🚨주의할 점은 내장된 자바스크립트 엔진이 싱글 스레드로 동작하는 것이지, 브라우저, node.js가 싱글 스레드 방식으로 동작하는 것이 아니다.
모든 자바스크립트 코드가 싱글 스레드 방식으로 동작하면 자바스크립트는 비동기로 동작할 수 없다.
자바스크립트 엔진은 싱글 스레드로 동작하지만, 브라우저는 멀티 스레드로 동작한다.

동기 처리 방식은 태스크를 순서대로 하나씩 처리하므로 실행 순서가 보장되지만 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹되는 단점이 있다.

비동기 처리 방식은 현재 실행중인 테스크가 종료되지 않아도 다음 태스크를 곧바로 실행해서 블로킹이 발생하지 않는다.

자바스크립트는 비동기 처리 싱글 스레드 방식으로 동작한다.

✔️이벤트 루프와 태스크 큐

대부분의 자바스크립트 엔진은 크게 두 개의 영역으로 구분한다.

  • 콜 스택
    자바스크립트 엔진은 단 하나의 콜 스택을 사용하기 때문에 최상위 실행 컨텍스트가 종료되어 파되기 전까지는 다른 어떤 태스크도 실행되지 않는다.

  • 힙은 객체가 저장되는 메모리 공간이다. 실행 컨텍스트는 힘에 저장된 객체를 참조한다.
    객체는 원시값과 달리 크기가 정해져 있지 않기 때문에 할당해야 할 메모리 공간 크기를 런타임에 결정해야 한다.
    따라서 힙은 구조화 되어있지 않다는 특징이 있다.

비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 또는 Node.js가 담당한다.

이를 위해 태스크 큐와 이벤트 루프를 제공한다.

  • 태스트 큐
    비동기 함수의 콜백 함수, 이벤트 핸들러가 일시적으로 보관되는 영역이다. 프로미스의 후소 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로태스크 큐도 존재한다.
  • 이벤트 루프
    현재 콜 스택에 실행 중인 실행 컨텍스트가 있는지, 태스크 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)이 있는지 반복해서 확인한다.
    콜 스택이 비어있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기중인 함수를 콜 스택으로 이동시킨다.

📒프로미스

프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체다

ES6에 도입되었으며 new 연산자와 함께 생성자 함수 Promise를 호출하면 프로미스(Promise 객체)를 생성한다.

Promise는 호스트 객체가 아닌 ECMAScript 사양에 정의된 표준 빌트인 객체다.

Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받으며 이 콜백 함수는 resolvereject 함수를 인수로 전달받는다.

프로미스는 현재 비동기 처리가 어떻게 진행되고 있는지를 나타내는 상태 정보를 갖는다.

프로미스의 상태 정보의미상태 변경 조건
pending비동기 처리가 아직 수행되지 않은 상태프로미스가 생성된 직후 기본 상태
fulfilled비동기 처리가 수행된 상태(성공)resolve 함수 호출
rejected비동기 처리가 수행된 상태(실패)reject 함수 호출

프로미스의 상태는 resolve 또는 reject함수를 호출하는 것으로 결정된다.

const fulfiled = new Promise(resolve => resolve(1));

또한 프로미스는 비동기 처리 상태 정보인 [[PromiseStatus]], 비동기 처리 결과 정보인 [[PromiseValue]] 내부 슬롯을 갖는다.

✔️프로미스 후속 처리 메서드

프로미스는 프로미스의 비동기 처리 상태가 변화되면 프로미스의 처리 결과를 가지고 후속 처리를 진행할 then, catch, finally 메서드를 제공한다.

프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다.

모든 후속 처리 메서드는 프로미스를 반환하며, 비동기로 동작한다. 프로미스를 반환하기 때문에

then 메서드는 언제나 프로미스를 반환한다. then 메서드가 프로미스가 아닌 값을 반환하면 이 값을 암묵적으로 resolve 또는 reject하여 프로미스를 생성해 반환한다.

✔️프로미스 에러 처리

catch 후속 처리 메서드를 모든 then 메서드 호출 이후에 호출하면 비동기 처리에서 발생한 에러(rejected 상태)뿐만 아니라 then 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있다.

✔️프로미스의 정적 메서드

Promise 생성자 함수는 5가지의 정적 메서드를 제공한다.

📌Promise.resolve/reject

이미 존재하는 값을 래픽해서 프로미스를 생성하기 위해 사용한다.

📌Promise.all

여러 개의 비동기 처리를 모두 병렬처리할 때 사용한다.

Promise.all()메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다. 인수로 전달받은 이터러블의 요소가 프로미스가 아닌 경우 암묵적으로 각 요소를 Promise.resolve메서드를 통해 프로미스로 래핑한다.

인수로 전달받은 배열의 모든 프로미스가 모두 fullfiled 상태가 되면 각 프로미스의 처리 결과를 배열에 저장해 새로운 프로미스를 반환한다.

Promise.all()메서드는 인수로 전달받은 배열의 프로미스 중 하나라도 rejected 상태가 되면 나머지 프로미스가 fulfilled 상태가 되는 것을 기다리지 않고 즉시 종료한다.
가장 먼저 rejected 된 프로미스를 반환한다.

📌Promise.race

프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는 것은 갖지만, 프로미스 요소 중 가장 먼저 fulfilled 상태가 된 프로미스를 반환한다.

프로미스가 rejected 상태가 되면 Promise.all과 마찬가지로 가장 먼저 rejected 된 프로미스를 즉시 반환한다.

📌Promise.allSettled

Promise.allSettled메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다.

전달받은 이터러블의 모든 프로미스가 settled 상태(fulfilled 혹은 rejected)가 되면 각 프로미스의 처리 결과를 나타내는 객체들을 배열에 담아 새로운 프로미스를 반환한다.

프로미스의 처리 결과를 나타내는 객체는 다음과 같다.

  • 프로미스가 fulfilled 상태인 경우 처리 결과를 나타내는 객체는 비동기 처리 상태 status 프로퍼티와 처리 결과 value 프로퍼티를 갖는다.
  • 프로미스가 rejected 상태인 경우 처리 결과를 나타내는 객체는 비동기 처리 상태 status 프로퍼티와 에러 reason 프로퍼티를 갖는다.

✔️마이크로태스크 큐

태스크 큐와 마이크로태스크 큐는 별도의 큐다.

프로미스 후속 처리 메서드의 콜백함수는 마이크로 태스크 큐에 저장되고 그 외의 콜백함수나 이벤트 핸들러는 태스크 큐에 저장된다.

마이크로태스크 큐가 태스크 큐보다 우선순위가 높기 때문에 이벤트 루프는 콜 스택이 비면 마이크로태스크 큐에 대기하고 있는 함수를 먼저 콜 스택에 푸시하고, 마이크로태스크 큐가 비면 태스크 큐에 대기하고 있는 함수를 콜 스택에 푸시한다.

📒

async/await은 프로미스를 기반으로 동작한다.

async/await을 사용하면 프로미스의 후속 처리 메서드 없이 동기 처리처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있다.

✔️async 함수

async 함수는 언제나 프로미스를 반환한다. async 함수가 명시적으로 프로미스를 반환하지 않아도 async 함수는 암묵적으로 반환값을 resolve 하는 프로미스를 반환한다.

✔️await 키워드

await 키워드는 반드시 async 함수 내부에서 사용해야 하며, 반드시 프로미스 앞에서 사용해야 한다.

await 키워드는 프로미스가 settled 상태가 될 때까지 대기하다가 resolve된 프로미스의 처리 결과를 반환한다.

profile
백엔드 개발자

0개의 댓글