Front/js

promise 처리 흐름 (콜백지옥, Promise, Async/Await)

읽히는 블로그 2024. 7. 3. 15:53

▤ 목차

     
     
     
     
    자바스크립트는 싱글스레드만 지원한다.
    기본적으로 여러개의 단위 프로그램(함수, 메서드)를 순서대로 진행한다. => 동기
    만약 순서대로 들어오는데 중간에 오랜 수행 시간이 필요한 작업이 들어오면 그 뒤에 들어온 작업들은
    그저 기다려야한다.
    만약 순차적 작업이 필요한 것이 아니라면 일을 병렬적으로 수행하여 일을 빠르게 처리할 수 있다. =>비동기
     
     

    ✔ 동기와 비동기

    프로그램 처리 흐름에 대해 배우기 이전에 동기와 비동기에 대해 정리해보고자 한다.
    클라이언트의 요청에 어떻게 반응해야할까?

    ⌨ 동기

    • 순서가 중요한 경우 사용한다.
    • 프로그램을 직렬적으로 수행한다.

    일이 들어오면 작업이 수행중이라면 다음 작업은 대기하게 된다.
     
    코드로 알아보자.

    function myProcess(){
    	const now = new Date();
    	const delay = now.getTime() + 3000; //현재 시간에 3초 추가 (딜레이는 3초)
    	
    	while(new Date().getTime() < delay){
    		//네트워크, 웹크롤링, 서버가 웹을 통해 클라이언트에게 제공된 자료 읽기
    	}
    	console.log('함수 수행 완료');
    }

     
     
     
    현재 시간에서 3초를 딜레이시켰다.
    이와 같은 과정이 진행될 것이다.
     

    console.log('안녕 첫번째 작업');
    myProcess();
    console.log('반가워 세번째 작업이야');

     
    결과

    바로 실행된다
    3초후 실행 결과

     
     
     

    💻 비동기 (setTimeout())

    • 순서가 상관없는 경우 사용한다.
    • 병렬적으로 수행한다.

     
     
    코드로 알아보자.

    function myProcess(){
    	//setTimeout() 비동기 처리용 함수
    	setTimeout(() => { //callback
    		console.log('함수 수행 완료')
    	},3000); //3초 후에 실행
    }

     
    해당 함수는 setTimeout() 함수를 사용해서 3초 후에  수행할 수 있도록 한다. 

    function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number

     
    위의 코드에서는 callback함수를 사용했다.
    설정한 시간(3초)뒤에 handler 함수를 실행해 달라는 의미의 함수이다.
     

    console.log('안녕 첫번째 작업');
    myProcess();
    console.log('반가워 세번째 작업이야');

    동기 방식과 동일하게 작업을 수행해보자.
    결과는 어떻게 나올까?
     

    수행하자마자

     

    3초 후

     
     
    콜스택에 쌓인 모든 실행 콘텍스트에 있는 처리들이 끝나면 비동기 실행 콘텍스트가 진행된다.

    Call Stack이 빈 상태가 되면, Callback Queue의 첫번째 콜백을 Call Stack으로 밀어넣는다.

     
    비동기 함수가 어떤 방식으로 진행되는지 찾아보니 Event Loop가 나온다.
     
     

    💻 Event Loop

    js는 단일 스레드이거나 콜백 대기열을 사용하고 있다.
    자바스크립트 엔진은 메모리 힙과 콜스택으로 구성되어 있다.
    즉, 단일 호출 스택이란 의미이다.
    콜스택은 기본적으로 프로그램 내 현재 위치를 기록하는 데이터 구조이다.
    함수에 들어가면 해당 함수를 스택 맨 위에 놓는다. 함수에서 돌아오면 스택의 맨 위에서 pop된다.
     
     
     

    https://medium.com/sessionstack-blog/how-does-javascript-actually-work-part-1-b0bacc073cf

     
    위의 그림의 오른쪽 하단을 보면 callback Queue라는 공간이 있다.
    비동기적으로 실행 콜백함수가 보관되는 영역이다.

    더보기

    Queue

    자료구조 중 하나이다. 선입선출의 룰을 따른다.

     

    1. V8(대표적 크롬) 엔진에서 call Stack에 쌓인다.
    2. stack에 쌓인 함수가 마지막에 들어온 함수부터 실행된다.
    3. 스택의 모든 함수가 실행되면 비동기 함수가 실행된다. 이때, Web API가 호출된다.
    4. Web API는 비동기 함수의 콜백함수를 callback Queue에 넣는다.
    5. Event Loop는 call stack을 주시하고있다가 비어지면 callback Queue에 있는 첫번째 콜백을 callStack으로 이동시킨다. (=> 이런 반복적인 과정을 tick이라고 한다.)

     
     

    👏 중요

    비동기 처리를 목적으로 나온 promise가 있다.
    이후에는 순차적으로 async / await , fetch가 등장했다.
    fetch는 나중에 ajax정리하고 정리하겠다.
    오늘은 promise와 async / await를 정리해보겠다.
     


     
     
     

    ✔ 콜백 지옥

    콜백 지옥은 js에서 비동기식 프로그래밍시 발생하는 문제이다.
    함수의 매개 변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.
     

    💻 콜백 함수

    콜백함수는 argument로 함수를 전달하는 함수를말한다.
    콜백 함수는 필요에 따라 바로 실행(synchronously)할 수도 나중에(asynchronously)실행할 수도 있다. 
     

    💻 콜백지옥

    콜백 지옥은 js에서 비동기식 프로그래밍시 발생하는 문제이다.
    이벤트 처리나 서버 통신 시 비동기적 작업을 수행하기 위해 발생하는 형태가 자주 등장한다.

    • 가독성이 떨어진다.
    • 코드 수정이 어려워진다.

     
    코드를 보자.

    function myProcess(){
    	//비동기 처리를 하는데 비동기 안에 중첩된 비동기 처리를 하면 아래와 같은 코드가 된다.
    	setTimeout(()=> { //난독이 된다.. 콜백지옥. 코드를 읽기 어렵다.
    	console.log('콜백1 완료');
    	setTimeout(()=> {
    		console.log('콜백2 완료');
    		setTimeout(()=> {
    			console.log('콜백3 완료');
    		},4000);
    	},3000);
    },2000);
    }

     
     
     

     


     
     
     

    ✔Promise


    콜백 지옥의 상황을 예방하기위해 표준 내장함수인 promise가 있다.
    프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다.
    다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '프로미스(promise)'를 반환한다.


    ⌨ 상태

     

     

    대기(pending):
    이행하지도, 거부하지도 않은 초기 상태

    이행(fulfilled):
    연산이 성공적으로 완료됨

    거부(rejected):
    연산이 실패함.


    대기중인 프로미스는 이행될 수도 어떤 오류로 인해 거부될 수도 있다.
    이행이나 거부될 때, 프로미스의 then 메서드에 의해 대기열(큐)에 추가된 처리기들이 호출됩니다.

    프로미스가 이행 혹은 거부인 경우, 보류가 아닌 경우에는  프로미스가 확정난 상태로 간주한다.

    resolved 라는 용어가 사용되면
    프로미스가 다른 프로미스의 최종 상태와 일치하도록 "settled"되거나 "locked-in"되어 더 이상 해결하거나 거부해도 아무런 효과가 없음을 의미합니다

    💻 코드로 보기

    const myPromise = (seconds) => new Promise((resolve, reject) =>{
    	setTimeout(() =>{
    		let a = 2; //실패 (3)를 경험해보자
    		if(a%2 ===0){
    			resolve('이 값이 then 메소드의 매개 변수로 전달됨');
    		}else{
    			reject('err : reject 실행되면 catch로 받을 수 있다.');
    		}
    		resolve('이 값이 then 메소드의 매개 변수로 전달됨');
    	},seconds);
    });

     
     
     
    JavaScript에서의 프로미스는 콜백 함수를 연결할 수 있는, 이미 진행 중인 프로세스를 나타낸다.


     


     
     


    ✔ Async/Await

    promise객체를 내부적으로 처리하는 Async-Await 라는 키워드가 있다.

    Async-Await는 promise 기반으로 작동한다.

     

    ⌨ 형식

    async function 함수명(){
    	try{
    		await 비동기 처리 메서드();
    	}
    catch(e){}
    finally{}}
    • await는 async에 종속된 키워드이다. 혼자 사용되지 않는다.
    • await는 적용된 함수는 promise를 반환한다.
    • 해당 함수는 promise가 실행될 때까지 대기
    • 처리가 완료되면 성공 또는 실패 처리를 한다.
    • 가독성이 좋은 비동기 처리 코드를 작성할 수 있다.
     function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환한다.

     

    💻 코드로 보기

    const myPromise = (seconds) => new Promise((resolve, reject) =>{
    	setTimeout(() =>{
    		let a = 2;
    		if(a%2 ===0){
    			resolve('이 값이 then 메소드의 매개 변수로 전달됨');
    		}else{
    			reject('err : reject 실행되면 catch로 받을 수 있다.');
    		}
    		resolve('이 값이 then 메소드의 매개 변수로 전달됨');
    	},seconds);
    });

     
     
     

    async function gogo(){
    	try{
    		const result1 = await myPromise(1000); //then 메서드 대신 적음
    		console.log(result1);
    		const result2 = await myPromise(2000);
    		console.log(result2);
    		const result3 = await myPromise(3000);
    		console.log(result3);
    		const result4 = await myPromise(4000);
    		console.log(result4);
    	}catch(e){
    		console.log('err : ',e);
    	}finally{
    		console.log('에러와 상관없이 실행');
    	}
    }

     
    try - catch문을 사용한다.
     
    자바스크립트는 await 키워드를 만나면 프라미스가 처리될 때까지 기다린다.
     promise then보다 가독성이 좋고 코드가 짧다.


    **일반함수에서 await는 사용하지 못한다.**
     


     
     

    😊정리

     await는 말 그대로 프라미스가 처리될 때까지 함수 실행을 기다리게 든다.
    프라미스가 처리되면 그 결과와 함께
    실행이 재개된다.
    프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.