[DAY17] JS 핵.심 동기 와 비동기

Jhey·2024년 11월 5일
0

JavsScript

목록 보기
13/18

0. 동기

  • 싱글 스레드를 사용하는 언어
  • 실행 순서가 순차적으로 진행된다.
function a() {
  console.log("a");
}

function b() {
  console.log("b");
}

a();
b(); // a 다음에 b가 출력된다.



1. 비동기

작업이 완료될 때까지 기다리지 않고, 오래 실행되는 작업을 시작하여 해당 작업이 실행되는 동안에도 다른 이벤트에 응답할 수 있게 하는 기술입니다. (동시성)

💡 일반적인 자바스크립트 엔진은 싱글 스레드로 하나의 Call stack을 가지는데, 어떻게 비동기적으로 실행될까?

➡️ JavaScript는 웹 브라우저에 탑재된 JavaScript 엔진을 통해 비동기 작업을 처리한다.
이 과정에서 Web API, Task Queue, 그리고 Event Loop가 각각 중요한 역할을 한다.



1.1 Task Queue 이해를 위한 Call stack 기본 규칙

  1. Call stack에 마지막에 들어간 실행 컨텍스트가 먼저 실행된다. (FILO)

  2. Call stack에서 위에 쌓인 실행 컨텍스트가 제거될 때까지 아래 컨텍스트는 실행되지 않는다.



1.2 Call stack 규칙과는 다른 비동기 작업

function fetchServer() {
  setTimeout(() => {
    console.log("time out");
  }, 1000);
}
function drawUI() {
  console.log("drawUI");
}

fetchServer();
drawUI();
console.log("콜스택 끝");
/* 결과:  
          drawUI
          콜스택 끝
          time out
*/

💡 Call stack 규칙대로라면 실행 순서는 time out -> drawUI -> 콜스택 끝이어야 한다. 그런데 어떻게 된 걸까?

➡️ 비동기 함수 setTimeout() 때문에 비동기 작업이 일어난 것이다.


비동기 작업 순서

  1. fetchServer 실행 컨텍스트콜스택에 들어감

  2. fetchServer 실행 컨텍스트웹 API용 기능이 포함되어 있음을 자바스크립트 엔진이 발견하고 웹 브라우저 자바스크립트 엔진에 이 기능을 맡김

  3. 3-1은 동시에 진행

    • 3-1. 콜스택에서 fetchServer 실행 컨텍스트제거

    • 3-1. 웹 브라우저 자바스크립트 엔진은 맡겨진 작업을 진행 (모든 비동기 함수가 동시에 실행)

    • 3-2. 작업이 끝나면 fetchServercallbackTask Queue에 넣어둠 (작업이 끝나는 순서대로 들어감)

  4. 콜스택에 다음 실행 컨텍스트가 들어가고 실행 후 제거됨

  5. 콜스택텅텅 빔

  6. 웹 브라우저의 Task Queue에 할 작업이 있는지 확인

  7. 있다면 등록 순서로 실행한다 (FIFO)


아래 그림은 소스와는 다르지만 비동기 작업 과정을 잘 보여준다. 출처

➡️ 그림을 통해 Call stack 실행 컨텍스트와 Web API가 동시에 실행되고, Call stack이 비워진 후 Task Queue 작업들이 Call stack에서 실행되는 것을 볼 수 있다.



2. 비동기 함수 다루기

비동기 함수가 여러 곳에 분포되어 있다면 어떻게 될까? 함수 실행 순서가 꼬여서 관리가 힘들 것이다. 이를 관리하기 위한 방법을 배워보자.

2.1 비동기 callback 함수

callback: 함수의 매개변수로 전달되는 함수

// 비동기적으로 실행되는 상태에서 활용되는 콜백 함수

// 원하는 순서: a -> b
// b()를 매개변수로 전달한다 (= callback 함수)
function a(callback) {
  setTimeout(() => {
    console.log("a");
    callback(); // b()가 실행된다.
  }, 0);
}
function b() {
  console.log("b");
}
a(b);

// 위의 과정을 아래처럼 축약할 수 있다.
a(function b() {
  console.log("b");
});

// 화살표 함수로도 나타낼 수 있다.
// 이는 화살표 함수의 탄생 배경이다 => 더 직관적인 callback 함수 표현을 위해
a(() => {
  console.log("b");
});

➡️ callback 함수를 사용하면 내가 원하는 순서로 비동기 함수를 제어할 수 있다.

callback 함수의 단점

function task1(callback) {
  setTimeout(() => {
    console.log("task1");
    callback();
  });
}

function task2(callback) {
  console.log("task2");
  callback();
}

function task3(callback) {
  console.log("task3");
  callback();
}

function task4(callback) {
  console.log("task4");
  callback();
}

// 콜백 지옥
task1(() => {
  task2(() => {
    task3(() => {
      task4(() => {
        console.log("done");
      });
    });
  });
});

➡️ 이곳저곳 흩뿌려져 있는 비동기를 callback으로 관리하기가 어렵다. 할 수는 있지만 콜백 지옥에 빠질 수 있다.


2.2 Promise

콜백 지옥을 경험하고 생겨난 Promise 패턴, 이 또한 비동기 함수 호출을 제어할 수 있는 패턴 중 하나이다.

Producer

: 비동기 작업을 실행하는 코드를 의미

➡️ 비동기 작업 실행 코드의 상태

  • pending ⇒ 코드가 실행되었으나 응답은 오지 않은 상태
  • fulfilled ⇒ 코드가 성공적으로 실행된 상태
  • rejected ⇒ 코드가 실패한 상태

요약된 그림은 아래와 같다. 출처

// 자바스크립트의 실행과 동시에 실행된다.
const promise = new Promise(function (resolve, reject) {
  console.log("Promise");
  resolve(); // 성공적으로 끝났음을 알림
  reject(); // 거절 알림
});

Consumer

: Promise의 결과를 소비하는 코드를 의미

➡️ Promise 인스턴스의 메서드

  • then => Promise를 리턴하고 두 개의 콜백 함수를 인수로 받음
  • catch => 프로미스가 거부될 때 호출될 함수를 예약
  • finally => 프로미스를 처리한 후 호출할 함수를 예약
// Promise 객체를 생성한다. 성공(resolve) / 실패(reject)
const promise = new Promise(function (resolve, reject) {
  console.log("Promise");
  resolve(); // 성공적으로 끝났음을 알림
  reject(new Error("failed")); // 실패로 에러 메시지(failed)를 보냄
});

// Promise 객체를 리턴한다.
promise
  .then((v) => {
    console.log(v);
  })
  // 오류를 호출한다. (failed)
  .catch((err) => {
    console.error(err); // 에러를 호출
  });

then() 체이닝

then 메서드는 Promise를 리턴하기 때문에, 이어지는 then 호출들을 손쉽게 연결할 수 있다.

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 1000);
});

fetchNumber
  // resolve의 전달값이 전달됨, Promise 값 반환
  // fetchNumber의 resolve 반환값 2가 then에 매개변수로 오고 그 값을 반환한다.
  .then((num) => num) // 2
  // then의 반환값 2가 then에 매개변수로 오고 그 값에 2를 곱하여 반환한다.
  .then((num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(num * 2); // 4
      });
    });
  })
  // then의 반환값 4가 then에 매개변수로 오고 그 값을 출력한다.
  .then((num) => {
    console.log(num); // 

4
  });
/*
	결과: 4    	
*/

➡️ then()은 유의미한 값을 반환하면 resolve를 반환하는 것과 같다.

➡️ then()은 명시적으로 Promise 객체를 반환해주는 것과 같다.

➡️ then() 체이닝 중 에러를 만나면 중단되고 catch가 실행된다.

Promise 정리

  • Promise 객체를 만들고 이를 반환한다 (resolve 또는 reject)
  • 그럼 이 반환값으로 then()을 체인으로 사용할 수 있다.
  • 반환값이 then()에 매개변수로 사용되고, 계속 유의미한 값을 넣으면 then이 연속된다.

➡️ .then , .catch , .finally가 프라미스를 반환하면 나머지 체인은 프라미스가 처리될 때까지 대기합니다. 처리가 완료되면 프라미스의 result(값 또는 에러)가 다음 체인으로 전달된다.

배운점

Promise는 진짜 생애 최초로 배우는 데 callback을 겪어보니 왜 만들어졌는지 알게 되었고 탄생 배경과 promise를 배우니 자바스크립트의 원리를 이해하는 데 도움이 되었다.

profile
천천히 가더라도 즐겁게 ☁

0개의 댓글

관련 채용 정보