배꼽파지 않도록 잘 개발해요

[엘리스sw] 4주차 1일 - 비동기통신 Promise 본문

교육/엘리스 SW 학습 내용

[엘리스sw] 4주차 1일 - 비동기통신 Promise

꼽파 2024. 1. 14. 15:02


◆ 자바스크립트 제어 흐름
이벤트 루프
◆  Promise


자바스크립트 제어 흐름

자바스크립트 비동기 이해하기
· 자바스크립트는 다른 멀티스레드 프로그래밍 언어(JAVA, C++)와 다른 방식으로 비동기 동작을 처리한다.
· 처음 자바스크립트를 접하는 경우, 동작에 대한 정확한 이해가 없으면 코드의 흐름을 따라잡기 어렵다.
· 자바스크립트 내부의 비동기 동작을 이해하기 위해서는 이벤트 루프 등의 개념을 알아야만 한다.

자바스크립트 엔진
· 자바스크립트 엔진은 하나의 메인 스레드로 구성된다.
· 메인 스레드는 코드를 읽어 한 줄씩 실행한다.
· 브라우저 환경에서는 유저 이벤트를 처리하고 화면을 그린다.

 

자바스크립트(비동기)와 자바(멀티스레드)

자바스크립트

  • 비동기 API가 호출되면(ex. setTimeout) 콜백함수가 즉시 실행되지 않는다. 대신 일정 시간이 지난 후 실행되도록 예약이 되어 있다. 브라우저나 Node.js 런타임은 이벤트 루프를 관리하며, 지정된 시간이 경과하면 콜백이 작업 대기열에 배치된다.
  • 자바스크립트의 기본 스레드는 작업 대기열(Task Queue)에서 작업을 실행하는 역할을 한다. 호출 스택이 비어있을 때마다 대기열을 확인한다. 
setTimeout(() => console.log('Hi'), 10000);

 

자바

  • 지연을 도입하거나 특정 기간 동안 기다리려면 'Thread.slee' 메서드를 사용할 수 있다.  
  • 현재 스레드가 일시 중지되고 전체 프로그램이 지정된 기간 동안 지연된다. 이 시간 동안 스레드는 다른 작업을 수행하는 데 사용할 수 없다.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            try {
                Thread.sleep(10000);
                System.out.println("Hi");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
    }
}

 

동기적 제어 흐름
· 동기적 제어 흐름은 현재 실행 중인 코드가 종료되기 전까지 다음 줄의 코드를 실행하지 않는 것을 의미한다.
· 분기문, 반복문, 함수 호출 등이 동기적으로 실행된다.
· 코드의 흐름과 실제 제어 흐름이 동일하다.
· 싱글 스레드 환경에서 메인 스레드를 긴 시간 점유하면, 프로그램을 멈추게 한다.

let a = 10;  // 선언문
console.log("a: ", a)  // 동기적 실행

// 반복문
function foo(num) {
	for (let i = 0; i < 10; ++i) {
		console.log(num)
	}
}

// 함수 호출
foo(num)

 

비동기적 제어 흐름
·  비동기적 제어흐름은 현재 실행 중인 코드가 종료되기 전에 다음 라인의 코드를 실행하는 것을 의미한다.
·  프로미스, 콜백 함수를 호출하는 함수 등은 비동기적으로 실행된다.
·  코드 흐름과 실제 제어 흐름이 다르다.
·  비동기 작업을 기다리는 동안 메인 스레드는 다른 작업을 처리한다.
·  비동기 등을 처리하는 모듈은 여러 스레드로 구성될 수 있다.

let a = 10;  // 1

setTimeout(function callback() {
	console.log('a : ', a)}
, 3000)  // 3 (3초 후 실행됨)

console.log('Finished');  // 2


이벤트 루프

자바스크립트 비동기
·   자바스크립트 엔진은 비동기처리를 제공하지 않고, 이벤트 루프는 자바스크립트 엔진 외부에 존재함.
·   대신 비동기 코드는 정해진 함수(API)를 제공하여 활용할 수 있다.
     ex. SetTimeout, XMLHttpRequest, fetch 등 Web API가 있음.
·   node.js의 경우 파일 처리 API, 암호화 API 등을 제공한다.

// 타이머 비동기 처리
setTimeout(() => console.log('타이머 끝'), 1000)
setInterval(() => console.log('인터벌 타이머'), 1000)
  • setTimeout: 지정된 지연(밀리초 단위) 후에 함수를 한 번 실행
  • setInterval: 지정된 간격(밀리초 단위)으로 함수를 반복적으로 실행
// 네트워크 처리
fetch('https://google.com')
	.then(() => console.log('네트워크 요청 성공.'))
	.catch(() => console.log('네트워크 요청 실패.'))

 

  • fetch : 네트워크 요청을 시작하고 해당 요청에 대한 응답으로 확인되는 Promise반환한다.
  • .then() : Promise가 해결되면(네트워크 요청이 성공한 경우) 콜백 함수를 실행한다.
  • .catch() : Promise가 거부된 경우(네트워크 오류가 있거나 응답 상태 코드가 오류를 나타내는 경우) 콜백 함수를 실행한다.

 

비동기 처리 모델
·    비동기 코드를 처리하는 모듈은 자바스크립트 엔진 외부에 있다.
·    이벤트루프(event loop), 태스크 큐(task queue), 잡 큐(job queue) 등으로 구성된다.
·    API 모듈은 비동기 요청을 처리한 후 태스크 큐에 콜백 함수를 넣는다.
·    자바스크립트 엔진은 콜 스택이 비워지면, 태스크 큐의 콜백함수를 들어온 순서대로 실행한다.

// 사용자 데이터 로드 시에 콜백 함수가 실행
request("user-data", (userData) => {
    // 사용자 데이터가 로드되었음을 나타내는 메시지 출력
    console.log("userData 로드");
    // 로드된 사용자 데이터를 처리하기 위해 `saveUsers` 함수 호출
    saveUsers(userData);
});

console.log("DOM 변경");
console.log("유저 입력");

Promise

Promise API

· Promise API는 비동기 API 중 하나이다.
· 태스크 큐가 아닌 잡 큐(Job queue 혹은 microtask queue)를 사용한다.
· 잡 큐는 태스크 큐보다 우선순위가 높다.

setTimeout(() => {
	console.log("타임아웃1");
}, 0);

Promise.resolve().then(() => console.log("프로미스1"));

setTImeout(() => {
	console.log("타임아웃2");
}, 0);

Promise.resolve().then(() => console.log("프로미스2"));

/*
프로미스1
프로미스2
타임아웃1
타임아웃2
*/

 

  • setTimeout : 지정한 시간(ms)만큼 기다렸다가 콜백 함수를 실행하는데, 이때 시간이 0ms라도 콜백함수는 현재 이벤트 루프가 끝난 후에 실행된다. 즉, 이전에 등록한 비동기 작업들이 먼저 실행된다는 의미이다.
  • Promise.resolve().then(...) : Promise 객체를 즉시 해결하고, 해당 Promise에 등록된 콜백 함수는 마이크로태스크 큐에 들어가게 된다. 마이크로태스크 큐는 이벤트 루프의 현재 iteration에서 처리되기 때문에, 이 콜백 함수들은 현재 iteration이 끝날 때 즉시 실행된다.
  • Promise의 콜백 함수가 마이크로태스크 큐에 들어가는 것과 setTimeout의 콜백 함수가 이벤트 큐에 들어가는 것의 차이로 인해 "프로미스1"과 "프로미스2"가 "타임아웃1"과 "타임아웃2"보다 먼저 실행된다.

 

Promise

·  비동기 작업을 표현하는 자바스크립트 객체
·  비동기 작업의 진행, 성공, 실패 상태를 표현함.
·  비동기 처리의 순서를 표현할수 있음.

 

Promise 생성자

new Promise(callback)

 

callback 함수는 (resolve, reject) 두 인자를 받는다.

  • Promise가 성공했을 때 : resolve를 호출
  • Promise 가 실패했을 때 : reject를 호출
// Promise 생성자를 사용하여 새로운 Promise 객체 생성
let promise = new Promise((resolve, reject) => {
    // Math.random()은 0(포함)과 1(미포함) 사이의 난수를 생성
    if (Math.random() < 0.5) {
        // 랜덤 조건이 참이면, Promise를 "실패" 이유로 거부
        return reject("실패");
    }

    // 랜덤 조건이 거짓이면, Promise를 값 10으로 이행
    resolve(10);
});
  • then() 메서드에 성공했을 때 실행할 콜백함수를 인자로 넘긴다.
  • catch() 메서드에 실패했을 때 실행할 콜백함수를 인자로 넘긴다.
  • finally() 메서드는 성공/실패 여부와 상관없이 모두 실행할 콜백 함수를 인자로 넘긴다.
  • then(callback1, callback2)로 callback1의 자리에 성공, callback2의 자리에 실패 메서드를 인자로 넘길 수 있다.
promise
	.then(data => {
		console.log("성공:", data)
	})
	.catch(e => {
		console.log("실패:", e)
	})
	.finally(() => {
		console.log("promise 종료")
	})

 

 

Promise 메서드 체인
· then/catch 메서드가 또 다른 Promise를 리턴하여, 비동기 코드에 순서를 부여한다.
· 이렇게 동일한 객체에 메서드를 연결할 수 있는 것을 체이닝(chaining)이라 한다.
· 함수를 호출한 주체가 함수를 끝낸 뒤 자기 자신을 리턴하도록 하여 구현한다.

promise
	.then(data => {
		return fetchUser(data)
	})
	.then(user => {
		console.log('User : ', user_
	})
	.catch(e => {
		console.log("실패: ", e)
	})

 

promise.resolve, promise.reject

·  Promise.resolve 함수는 성공한 Promise를 바로 반환한다.
·  Promise.reject 함수는 실패한 Promise를 바로 반환한다.
·  인위적으로 Promise 메서드 체인을 만들 수 있다.
·  비동기 코드로 진행해야 하는 상황 등에 유용하게 사용할 수 있다.

Promise
	.resolve(10)
	.then(console.log)

Promise
	.reject("Error")
	.catch(console.log)

 

Promise.all

·  Promise.all은 Promise의 배열을 받아 모두 성공 시 각 Promise의 resolved 값을 배열로 반환한다.
·  하나의 Promise라도 실패할 시, 가장 먼저 실패한 Promise의 실패 이유를 반환한다.

Promise.all([
    promise1,
    promise2,
    promise3
])
    .then(values => {
        console.log("모두 성공:", values);
    })
    .catch(e => {
        console.log("하나라도 실패:", e);
    });

디바운싱과 쓰로틀링

디바운싱(Debouncing)

  • 특정 이벤트(키보드 입력, 스크롤 이벤트)가 여러 번 발생할 때, 마지막 이벤트가 발생한 후에만 특정 동작을 수행하는 기술
  • 예시 : 텍스트를 입력할 때 자동으로 검색 결과를 가져오는데, 사용자가 연속적으로 텍스트를 입력하는 경우에 매번 검색을 수행하지 않고, 마지막 텍스트 입력 이후 일정 시간 동안 대기한 뒤에 검색을 수행하는 것
// Debouncing function

function debounce(func, delay) {
  let timer;

  // 실제로 호출될 함수를 반환
  return function () {
    // 함수가 호출될 때의 컨텍스트와 인자를 저장
    const context = this;
    const args = arguments;

    // 이전에 설정된 타이머를 취소 
    // 모았다가 한번에 해야됨.
    clearTimeout(timer);

    // delay 시간 후에 실제 함수를 호출하는 타이머를 설정
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

 

쓰로틀링(Throttling)

  • 특정 이벤트의 발생을 제한하여 일정 시간 동안 해당 이벤트가 여러 번 발생해도 특정 동작이 너무 자주 실행되지 않도록 하는 기술
  • 예시 : 무한 스크롤 혹은 페이지 스크롤 이벤트에서, 사용자가 스크롤할 때마다 많은 이벤트가 발생할 수 있음. 이때 이벤트 발생 횟수를 제한함.
// Throttling function

function throttle(func, delay) {
  let throttled = false;

  // 실제로 호출될 함수를 반환
  return function () {
    // 함수가 호출될 때의 컨텍스트와 인자를 저장
    const context = this;
    const args = arguments;

    // 쓰로틀링이 적용되지 않은 경우
    if (!throttled) {
      // 실제 함수를 호출
      func.apply(context, args);
      
      // 쓰로틀링 플래그를 설정
      throttled = true;

      // delay 시간 후에 쓰로틀링 플래그를 초기화하는 타이머를 설정
      setTimeout(() => {
        throttled = false;
      }, delay);
    }
  };
}
728x90