[Node.js] 동기와 비동기 | Blocking , Non-Blocking

김민욱·2022년 4월 24일
0

Node.js

목록 보기
1/2

동기(Synchronous)

어떤 작업이 끝나기 전까지 다음 작업 실행을 하지 않고 요청에 대한 응답이 완료될 때까지 기다린다.
I/O가 끝날때 까지 작업이 중단, 대기하고 백그라운드 작업 완료 여부를 계속 확인한다.


비동기(Asynchronous)

작업의 완료 유무와 상관없이 동시에 다음 작업 수행이 가능하다. 요청만 보내고 다시 프로그램을 처리한다.
백그라운드가 완료 알림을 주면 미리 지정한 이벤트를 처리한다.


비동기 방식으로 처리되는 Node.js

Node.js는 비동기 방식으로 처리한다. 비동기 방식을 사용하는 이유는 Node는 Javascript를 기반으로 한 하나의 환경이다. Javascript는 기본적으로 Single Thread 언어여서 한번에 한 작업을 처리한다.
<한가지 여기서 알아야 할 점은 Node.js는 프레임워크가 아닌 그저 단순한 자바스크립트 실행 환경이다.>

Multi Thread(병렬처리)는 요청을 Thread에서 처리하도록 CPU 자원을 분할한다. 하지만 자원은 한계가 있고 트래픽이 큰 작업을 처리하려면 서버를 업그레이드를 하는 등 자원 문제를 해결할 필요가 있다.

따라서 비동기 처리가 필요한 이유는 동기 처리시에 백그라운드가 작업하는 동안 메인 스레드는 대기하게 돼서 일하지 않는 경우가 발생하여 매우 비효율적이다.

Node 에서는 비동기 처리를 Thread가 아닌 Event 방식으로 해결을 한다. 그 방식에는 총 3가지가 있다.
callback Function, promise, Async/Await 이 있다. 먼저 callback과 promise에 대해서 알아보고 다음 포스팅에서 Async/Await에 대해서 알아보자.



Callback Function

이벤트가 발생하고 특정 시점에 도달했을 때 시스템에서 호출하는 함수를 뜻한다. 다른 함수의 인자로서 사용이 가능하다.

Javascript에서 비동기와 callback으로 setTimeout을 들 수 있다.

console.log('시작');

setTimeout(()=>{
    console.log('Set Time out');
    
}, 2000)

console.log('끝');

const condition: boolean = true;

const promise = new Promise((resolve, reject) =>{
    if(condition){
        resolve('성공');
    }else{
        reject(new Error('에러'))
    }
});

하지만 callback 함수로 인해 코드가 중첩이 되다보면 가독성이 떨어지거나 에러 처리가 애매해지는 경우가 발생한다. 예를들면 아래의 코드와 같다.

setTimeout((name): void => {
	nameList = [nameList, name];
    console.log(serverList);
    
    	setTimeout((name): void => {
			nameList = [nameList, name];
    		console.log(serverList);
            
            setTimeout((name): void => {
				nameList = [nameList, name];
    			console.log(serverList);
            }, 1000, '김민욱');
       }, 1000, '우기');
 }, 1000, '욱');

이처럼 코드의 가독성이 떨어지기 때문에 이를 보완하기 위해 promise가 나왔다. promise는 callback의 단점을 보완하고 비동기 처리를 명확히 표현할 수 있는 장점이 있다.



Promise

위에서 말했든 promise는 callback 함수의 단점을 보완하고 비동기 처리를 명확히 표현할 수 있다라는 장점이 있다.

promise는 총 3가지 상태가 있다.
1. Pending
2. Fullfiled
3. Rejected


Pending(대기) - 비동기 처리가 완료되지 않은 상태

pending은 최초 생성 시점을 말하는데 new Promise()호출 시에 콜백함수를 선언해야 한다.

const promise = new Promise((resolve, reject) =>{});

여기서 resolve는 동작에 대한 결과를 올바르게 줄 수 있다는 것이고, reject는 동작을 실패하면 호출한다.



Fullfiled(이행) - 비동기 처리가 완료되어 Promise 결과를 반환

작업을 성공적으로 완료한 상태이고 resolve() 실행이 정상적으로 이행된 상태에 then()을 통해 전달한다. 코드는 아래와 같다.

const promise = new Promise((resolve, reject) =>{
     if(condition){
         resolve('성공');//resolve는 성공했을때
     }
});

promise.then((resolveData): void => console.log(resolveData));

resolve() 실행이 정상적으로 이행된 상태에 then()을 통해 전달한다.



Rejected(실패) - 실패하거나 오류

작업이 실패한 상태이고 catch()를 통해 전달한다. 코드는 아래와 같다.

const condition: boolean = true;

const promise = new Promise((resolve, reject) =>{
     if(condition){
         resolve('성공');//resolve는 성공했을때
     }
});

promise.then((resolveData): void => console.log(resolveData));

resolve() 실행이 정상적으로 이행된 상태에 then()을 통해 전달한다.

const condition: boolean = false;

const promise = new Promise((resolve, reject) =>{
    if(condition){
        resolve('성공');//resolve는 성공했을때
    }else{
        reject(new Error('reject !! error'));
    }
});

promise.then((resolveData): void =>{
    console.log(resolveData); 
}).catch(error => console.log(error)); //reject는 catch를 통해 전달됨.



Promise Chaining - 여러개의 프로미스를 연결해 사용

<Promise>.then(): 비동기 작업 완료 시 결과에 따라 함수를 호출
<Promise>.catch(): 체이닝 연결 상태에서 중간에 에러가 났을 때 호출

다음과 같은 코드를 들 수 있다.

const restaurant = (callback: () => void, time: number) =>{
    setTimeout(callback, time);
}

//주문
const order = (): Promise<string> => {
    return new Promise((resolve, reject)=>{
        restaurant( () =>{
            console.log('레스토랑 진행상황 - 음식주문');
            resolve('음식 주문 시작');
        },1000);
    });
}

//요리
const cook = (progress: string): Promise<string> =>{
    return new Promise((resolve,reject)=>{
        restaurant(()=>{
            console.log('[레스토랑 진행상황 - 음식 조리 중]');
            resolve(`${progress}->음식 조리 중`);
        }, 2000);
    });
}

//서빙
const serving = (progress: string): Promise<string> => {
    return new Promise((resolve,reject)=>{
        restaurant(()=>{
            console.log('[레스토랑 진행 상황 - 음식 서빙 중]');
            resolve(`${progress}->음식 서빙 중`);
        }, 2500);
    });
}

//식사
const eat = (progress: string): Promise<string> =>{
    return new Promise((resolve, reject)=>{
        restaurant(()=>{
            console.log('[레스토랑 진행상황 - 음식 먹는 중]');
            resolve(`${progress}->음식 먹는 중`);
        },3000);
    });
}

//주문
order()
    .then(progress => cook(progress))
    .then(progress => serving(progress))
    .then(progress => eat(progress))
    .then(progress => console.log(progress));



그리고 Promise는 여러개여도 catch는 단일하다는 예시는 아래와 같다.

Promise.resolve(123)
    .then(res=>{
        throw new Error('에러 발생');
        return 456;
    })
    .then(res =>{
        console.log(res);
        return Promise.resolve(789);
    })
    .catch(error =>{
        console.log(error.message);
        
    });

출처: sopt 서버파트 30기 2차 세미나 파트장 채정아님의 자료

profile
열쪙 열쪙 열쪙

0개의 댓글