[데브코스/TIL] DAY23 - JavaScript(10) 콜백함수와 Promise

Minha Ahn·2024년 11월 5일
0

데브코스

목록 보기
20/29
post-thumbnail

🌀 동기와 비동기

1. 동기

🔎 동기란?

먼저 시작된 하나의 작업이 끝날 때까지 다른 작업을 시작하지 않고 기다렸다가, 다 끝나면 새로운 작업을 시작하는 방식

  • 자바스크립트는 동기 언어 & 싱글 스레드 언어
  • 그렇지만 자바스크립트로 비동기를 구현할 필요성 있음
    • 자바스크립트의 비동기 실행을 도와주는 것이 웹 브라우저의 자바스크립트 엔진

2. 비동기

🔎 비동기란?

먼저 시작된 작업의 완료 여부와는 상관없이 새로운 작업을 시작하는 방식

3. 웹 브라우저의 자바스크립트 엔진

웹 브라우저 자바스크립트 엔진 종류

  • 크롬 - v8 엔진
  • 파이어폭스 - 스파이더몽키 엔진
  • 사파리 - 자바스크립트코어 엔진

대표적인 Web API의 비동기 함수

  • setTimeout(), setInterval(), fetch()
  • Web API란
    • 웹 기반 애플리케이션과 상호 작용하기 위해 설계된 API
    • 일반 자바스크립트 엔진에 탑재된 것이 아닌, 웹 브라우저에서부터 제공받는 것
    • 공통적인 특징 : 콜백 함수가 존재한다.
  • 일반 자바스크립트 엔진이 아닌, 웹 브라우저 자바스크립트 엔진에서 실행
    • 일반적인 JS 코드는 일반 자바스크립트 엔진에서 실행

4. 비동기 Web API 활용의 동작 과정

function fetchServer() {
  setTimeout(() => {
    console.log("Fetch Server");
  }, 1000);
}

function drawUI() {
  console.log("Draw UI");
}

fetchServer();
drawUI();
Draw UI
Fetch Server

실행 컨텍스트의 흐름

  1. 전역 컨텍스트 생성 & 실행
  2. fetchServer 함수의 실행 컨텍스트 생성 & 실행
  3. fetchServer 실행 종료 & 실행 컨텍스트 삭제
  4. drawUI 함수의 실행 컨텍스트 생성 & 실행
  5. drawUI 실행 종료 & 실행 컨텍스트 삭제
  6. 전역 컨텍스트 실행 종료 & 삭제

실행 결과를 통해 fetchServer에서 1초 뒤에 실행할 console.log가 출력하기도 전에 실행 컨텍스트가 삭제 되어, drawUI 실행 컨텍스트가 생성 및 실행되었다는 사실을 알 수 있다.

동작 과정

5. 이벤트 루프

🔎 이벤트 루프란?

JavaScript에서 비동기 작업을 관리하고 처리하는 핵심 메커니즘(기본적인 원리나 구조)

이벤트 루프가 하는 일

  • 콜스택이 비어있는지 확인하기
  • 태스크 큐에 대기 중인 콜백 있는지 확인하기
  • 대기 중인 콜백이 있다면 콜스택으로 옮겨주기

질문

  • 웹 브라우저 엔진은 setTimeout을 여러 개 받으면 setTimeout을 동시에 실행시키는건가요?
    • 그렇습니다. 각각 독립적으로 처리해요.



📞 콜백함수

🔎 콜백함수(callback)란?

다른 함수에 매개변수로 전달되어 그 함수가 실행되는 동안 특정 시점에 호출되는 함수

1. 동기 콜백 함수

  • 콜백 함수가 동기적(위에서 아래로, 흐름대로)으로 실행되는 경우
function a(callback) {
  console.log("a");
  callback();
}

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

a(b);

2. 비동기 콜백 함수

  • 특정 비동기 함수의 실행이 종료된 후에 실행되는 경우
function a(callback) {
  setTimeout(() => {
    console("a");
    callback;
  }, 0);
}

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

a(b);

// 화살표 함수도 가능 (간단! 화살표 함수가 탄생하게 된 배경?!)
a(() => { console.log("b"); })

3. 콜백 지옥

  • 콜백 함수를 중첩하여 사용할 때 나타나는 현상
  • 코드 복잡 & 가독성 저하
  • 어떻게 해결하지?
    • 콜백 함수를 외부로 분리
    • 프로미스 활용
    • async/await 활용
  • 그렇다고 콜백함수가 무조건 나쁘다는 건 아니다!
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("all task completed");
      });
    });
  });
});



🤙 Promise

🔎 Promise란?

자바스크립트 비동기 작업을 처리하기 위한 객체로, 비동기 작업의 성공 또는 실패를 나타내는 정보 소유

1. 사용하는 이유

  • JS에서 비동기 작업의 실행 순서를 보장하기 위해
    • then 키워드를 순차적으로 연결하면 원하는 순서로 실행 가능
  • 비동기 코드의 가독성과 유지보수 향상을 위해

2. 문법

new Promise(function(resolve, reject) {
    // 비동기 작업을 수행하는 코드
    // 작업이 성공하면 resolve(value) 호출
    // 작업이 실패하면 reject(error) 호출
});
  • Promise 인스턴스 생성하여 비동기의 작업의 완료를 나타냄
  • Promise의 콜백함수 (executor) : resolve와 reject를 받는 함수
    • resolve: 성공하면 호출, 인자 전달 가능 (최대 1개)
    • reject : 실패하면 호출, 인자 전달 가능 (최대 1개)
  • resolve와 reject는 JS가 성공 실패 여부를 판단해서 알아서 호출하는 것이 아님.
    • resolve를 호출했다는 것 자체가 성공을 의미.
    • 즉, 언제 resolve를 실행하고 언제 reject를 실행할지를 결정하는 코드 필요

3. 상태

Promise의 상태는 3가지 중 하나

  • pending : 코드가 실행되었으나 응답은 오지 않은 상태
    • resolve나 reject 중 그 어느 함수도 호출되지 않았을 때
  • fulfilled : 코드가 성공적으로 실행된 상태
    • resolve가 호출되었을 때
  • rejected : 코드가 실패한 상태
    • reject가 호출되었을 때

4. Producer와 Consumer

Producer

  • 데이터를 생성하거나 이벤트를 발생시키는 주체 & 성공 여부 반환
  • 예시) API 요청 & 데이터 받아오는 함수
const promise1 = new Promise(function (resolve, reject) {
  console.log("promise...");
  resolve(); // 성공 (fulfilled)
});

const promise2 = new Promise(function (resolve, reject) {
  console.log("promise...");
  reject(new Error("실패!")); // 실패 (rejected) 인자 삽입 가능
});

이런 식으로 상황에 따라 성공 실패 여부 전달

const promise = new Promise((resolve, reject) => {
  console.log("doing something...");
  const isSuccess = false;
  setTimeout(() => {
    isSuccess ? resolve("success") : reject(new Error("no network"));
  });
});

Consumer

  • Producer가 생성한 데이터를 소비하거나 처리하는 주체
  • then, catch, finally로 소비
    • then : fulfilled일 때 실행
    • catch : rejected일 때 실행
    • finally : 상태 관계없이 무조건 실행
  • 위에서부터 순차적으로 넘어가기 때문에 위치에 따라 결과 달라질 수 있음
    • finally가 제일 위에 있다면 finally 먼저 실행 -> 그 이후 실행
    • catch가 제일 위에 있고, catch 이후의 then에서 에러가 발생할 경우, 받아줄 catch가 없음
promise2
  .then((data) => {
    // data는 resolve가 받아온 인자
    console.log(data);
  })
  .catch((err) => {
    // err는 resolve가 받아온 인자
    console.log(err);
  })
  .finally(() => {
    // 받을 수 있는 매개변수 없음
    console.log("finally");
  });

4. Promise 체이닝

  • 반환값을 통해 다음 then으로 전달 가능
  • 중간에 catch가 있어도 값을 반환하면 다음 then으로 받기 가능
const getSunIcon = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("🌞");
    }, 1000);
  });

const getWaterDropIcon = (sun) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve(`${sun} => 💧`);
      reject(`${sun}`);
    }, 1000);
  });

const getPlantIcon = (water) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${water} => 🌱`);
    }, 1000);
  });

const getFruitIcon = (plant) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${plant} => 🍎`);
    }, 10);
  });

getSunIcon()
  .then((sun) => getWaterDropIcon(sun))
  .catch((error) => `${error} => 🌧`)
  .then((water) => getPlantIcon(water))
  .then((plant) => getFruitIcon(plant))
  .then((fruit) => console.log(fruit));





📌 출처

수코딩(https://www.sucoding.kr)

profile
프론트엔드를 공부하고 있는 학생입니다🐌

0개의 댓글