TIL | <JavaScript> 비동기적 처리, Promise, async

bubblegum·2024년 1월 9일
0

Today I learn(TIL)

목록 보기
10/84
post-thumbnail

코드의 처리 과정을 크게 동기적 과정과 비동기적 과정으로 구분할 수 있다. 오늘은 이 두 처리 과정에 대해서 더 자세히 알아보고 비동기적 처리가 가진 문제와 더불어 promise와 async & await문을 함께 알아보고자 한다.

1. 동기와 비동기

동기(synchronous)적 처리 과정은 현재 실행 중인 코드가 끝나야 다음 코드를 실행하는 방식이다. CPU의 계산에 의해 처리가 가능한 대부분의 코드를 동기적 코드라고 말한다.

반면 비동기(asynchronous)적 처리 과정은 실행 중인 코드의 완료 여부와 상관없이 즉시 다음 코드를 처리하는 방식이다. 별도의 요청, 실행 대기, 보류 등과 관련된 모든 코드는 비동기적 코드이다. 예를 들어 이벤트 처리 및 서버 통신이 해당된다. 웹의 복잡도가 올라갈수록 비동기적 코드의 비중이 늘어나며 이에 따라 이름하여 "콜백 지옥"이라는 현상이 빈번히 나타난다.

콜백 지옥(Callback Hell)
: 비동기적 처리로 인한 오류를 해결하기 위해 반복적으로 함수 안에 함수를 계속 선언하는 것.

이러한 콜백 지옥 현상을 해소하기 위해 여러 가지 문법들이 Javacript에 도입되었다.

2. Promise

ES2015(ES6)에 등장한 개념으로, 비동기적으로 실행되는 코드 흐름 안에서 특정 이벤트의 성공(fulfill), 실패(reject)와 같은 결과를 다음 문장으로 전달하겠다는 약속의 의미를 담은 객체이다.

Promise 는 객체이기 때문에 Promise 생성자 함수를 통해 instance가 만들어지고, 생성자 함수 내에 전달 되는 함수를 실행자(executor)라고 한다. promise는 new Promise 로 instance를 만들때 바로 executor가 자동으로 실행된다!

Promise 를 생성할 때, 비동기적 작업의 성공 혹은 실패 여부에 따라 다른 결과를 전달할 수 있게 하는 매개변수로 resolve() 와 reject() 라는 callback 함수를 가진다.

let a = 1;
console.log("프로그램 코드 시작");
const myPromise = new Promise((resolve, reject) => // 프로듀서
    console.log("프로미스 생성!");
    if (a === 1) resolve(a);
    else reject(new Error("a가 1이 아닙니다!"));
});
myPromise.then(value => { // 컨슈머
    console.log(value);
});
console.log("프로그램 코드 끝");
  • 프로듀서(producer): 프로듀서는 Promise를 생성하고 비동기 작업을 수행하는 역할을 합니다. Promise 생성자 안에서 비동기 작업이 이루어집니다. 주로 네트워크 요청, 파일 읽기, 타이머 등의 비동기 작업을 처리합니다.

  • 컨슈머(consumer): 컨슈머는 프로듀서가 생성한 Promise의 결과를 소비하고 처리하는 역할을 합니다. 이는 주로 .then() 및 .catch() 메서드를 사용하여 Promise의 성공 또는 실패를 처리하는 부분입니다.

기본적으로 아래 3가지 상태를 가진다.

  1. 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
  2. 이행(fulfilled): 연산이 성공적으로 완료됨.
  3. 거부(rejected): 연산이 실패함.

3. async await문

ES6에서의 promise 만으로는 비동기/동기 제어가 여전히 불편했고, 여전히 callback 지옥에서 "쉽게" 벗어날 수 없었다. 그래서 ES2017, ES8 에서 async, await 문법이 등장했다.

async & await를 뭔가가 새로운 object 라고 이해할 수 있지만, 여전히 promise object를 control할 뿐이고, 이런 promise 사용을 "편하게" 해주는 "syntactic sugar" 일 뿐이다.

syntactic sugar 란?
문법에 설탕을 뿌려 더 달콤하게 사용한다는 의미
문법적으로 더 읽기 쉽고 이해하기 편하도록 표현되어지는 것

const test = async () => { return "test"; };
const a = test();
console.log(a);
a.then((value) => console.log(value));

await 문은 Promise가 fulfill되거나 reject 될 때까지 async 함수의 실행을 일시 정지하고, Promise가 fulfill되면 async 함수를 일시 정지한 부분부터 실행합니다. 이때 await 문의 반환값은 Promise 에서 fulfill된 값이 됩니다.(Promise ~ then문과 동일한 효과)

만약 Promise가 reject되면, await 문은 reject된 값을 throw합니다.

await 연산자 다음에 나오는 문의 값이 Promise가 아니면 해당 값을 resolved Promise로 변환시킵니다.

  • throw
    throw 문은 사용자 정의 예외를 발생(throw)할 수 있습니다. 예외가 발생하면 현재 함수의 실행이 중지되고 (throw 이후의 명령문은 실행되지 않습니다.), 제어 흐름은 콜스택의 첫 번째 catch (en-US) 블록으로 전달됩니다. 호출자 함수 사이에 catch 블록이 없으면 프로그램이 종료됩니다.
  1. Generator

제너레이터(Generator) 함수에는 ' * '가 붙어 있다. 제너레이터를 실행하면, 'next()'를 가지고 있는 반복될 수 있는(iteratable) 객체인 'iterator'가 반환된다. next 메소드를 호출하면, 제너레이터 함수 내부에서 가장 먼저 등장하는 'yield'에서 비동기적 처리를 멈춘다.

    function* generator() {
      yield 1;
      yield 2;
      yield 3;
    }

    const gen = generator(); // "Generator { }"

    console.log(gen.next().value); // 1
    console.log(gen.next().value); // 2
    console.log(gen.next().value); // 3
profile
황세민

0개의 댓글

관련 채용 정보