[JS/Node] 비동기

FeelSoo·2022년 4월 13일
0

CodeStates

목록 보기
14/43

비동기란?

멀티 태스킹을 의미한다. 동기적 실행은 수행하고 있는 기존 작업이 완료되기 전까지 다른 작업을 진행할 수 없지만 비동기적 실행은 다중 실행이 가능한 시스템이다.

동기적 실행은 주어진 구문을 순차적으로 실행한다면 비동기적 실행은 특정 구문을 몰아서 처리할 수 있다.

<동기적 실행 구문>


보면 맨 마지막 구문이 총체적인 실행 구조고 그 위의 변수 및 함수들이 구조에 들어가는 인자들이다.

forEach로 customers의 인자를 순회하여 실행할건데 costomers.request 즉, 메뉴에 대한 인자들을 orderCoffeSync 함수에 집어넣은 결과를 얻을 것이다.

그 값으로 '메뉴'가 접수되었습니다 & 4초 뒤 '메뉴'가 출력될 것이다.

즉, let coffee = '메뉴'가 접수되었습니다 & 4초 뒤 '메뉴' 이고 전자의 값은 console 출력값이기에 남는 값은 '메뉴'이다.

이후 이 '메뉴'가 drink 함수에 들어가서 customers.name의 인자와 같이 조합되어 출력될 것이다.

콘솔 출력값을 보면 순차적으로 실행하다보니 하나의 접수가 완료되기 전까지 다른 접수를 받지 못하고 있다.



<비동기적 실행 구문>


이번에는 비동기 흐름을 살펴보자.

메뉴가 orderCoffeAsync로 들어가는데 drink 함수를 갖는 콜백 함수와 같이 들어간다.

orderCoffeAsync를 살펴보니 콜백함수가 4초 뒤에 실행된다.

즉, 주문이 들어가고 drink 함수가 4초 뒤에 실행된다.

다시 말해 카페라떼 건을 받으면 4초 뒤에 처리할 테스크를 남겨두고 새로운 주문을 받을 수 있게 된다.

그래서 접수를 바로바로 하고 남겨진 테스크들을 몰아서 처리할 수 있게 된 것이다.





비동기적 실행을 예로 들면 우리는 모바일로 뮤직 플레이어를 실행시키면서 카카오톡을 실행할 수 있다. 동기적 실행 시스템이었다면 음악을 중지시킨 후 카카오톡 메시지를 전송할 수 있었을 것이다.


JavaScript에서 비동기적 시스템을 구축할 수 있게 도와주는 라이브러리는 Node.js이다.
callback, promise, async/await 중 하나의 문법을 이용하여 구현할 수 있다.


대표적으로 사용될 수 있는 작업은 다음과 같다

a. 백그라운드 실행, 로딩 창 등의 작업
b. 인터넷에서 서버로 요청을 보내고, 응답을 기다리는 작업
c. 큰 용량의 파일을 로딩하는 작업



Achievement Goals

어떤 경우에 중첩된 callback이 발생하는지 이해할 수 있다.

중첩된 callback의 단점, Promise의 장점을 이해할 수 있다.

Promise 사용 패턴을 이해할 수 있다.

resolve, reject의 의미와, then, catch와의 관계를 이해할 수 있다.

Promise에서 인자를 넘기는 방법을 이해할 수 있다.

Promise의 세 가지 상태를 이해할 수 있다.

Promise.all 의 사용법을 이해할 수 있다.

async/await keyword에 대해 이해하고, 작동 원리를 이해할 수 있다.

Node.js의 fs 모듈의 사용법을 이해할 수 있다.



비동기를 조금은 동기적으로

비동기적 흐름을 구현하는 방법 중 하나로 고차함수가 쓰이는 만큼 리뷰가 필요하다.

고차 함수는 Callback function이라고도 하며 다른 함수를 인자로 받을 수도 있고 함수를 리턴할 수도 있다.

그리고 비동기 흐름을 유지하면서 특정 부분에서는 동기적으로 진행될 수 있게 제어할 수 있게 만드는 함수다.

비동기 흐름을 동기적으로 만든다? 말이 조금 어색할 수 있겠지만 다음의 예시를 보면 이해가 갈 것이다.

커피집에서 주문을 받는데 많은 주문이 밀려있는 상황이다. 그런데 긴급건이 들어와서 밀려있는 주문들 말고 방금 들어온 주문을 처리해야 하는 경우도 생길 것이다.

이렇게 긴급건을 처리하기 위해선 비동기적 흐름을 유지하면서 순차적으로 제어할 필요도 생기는데 이 때에도 콜백 함수를 이용한다.

혹은 밥집 갔는데 A가 먼저 제육볶음을 시켰고, B는 갈비탕을 시켰는데 누가 먼저 받을까? 알 수 없다. 요청을 처리하는데 걸리는 시간이 다르기 때문이다. 하지만 A가 먼저 요청했으니 먼저 받고 싶어해서 우선 제공해야하는 경우가 생긴다. 순차적으로 제어를 하겠다는 말이다.

위에서는 커피와 밥집으로 예로 들었지만 서버 시스템이나 어떤 어플리케이션 기능을 설계할 때 때로는 우선 처리 할 수 있게끔 동기적인 흐름을 유지하도록 만들 필요가 생길 것이다.

위의 사진을 보면 printString은 문자형과 callback 함수를 인자로 받아 setTimeout ( (func), math.floor(~) ) 기능을 실행하는 변수이다. 즉, mathfloor초 ( 랜덤 시간 ) 뒤에 func를 실행시키는 변수다. 그 func는 받은 문자를 console로 출력하고 callback 함수를 호출하는 함수다.

이제 밑의 printAll을 한 번 살펴보자. printAll은 printString의 콜백함수 안에서, 그 다음 콜백 함수 안에 종속되어 실행되는 것을 알 수 있다. 종속되어 있으니 요청을 순차적으로 제어할 수 있음을 확인했다.

callback으로 순차제어를 구현한 것이다.

하지만 이렇게 callback을 남용하다간 callbackhell이라는 좋지 못한 결과를 얻을 수도 있다.



( callback hell example )

이를 방지하기 위해 Promise를 사용할 수 있다.

promise ----- call back hell을 방지하기 위한 함수.

중복 콜시 call back은 에러 케이스를 매 콜마다 구현해야하지만 promise는 중복콜 마지막에 error시 .catch로 한 케이스만 구현해도 됨.

Promise는 resolve와 reject 두 콜백 함수를 인자로 받는다.

resolve는 에러가 없을 때 진행되는 함수고

reject는 에러가 발생했을 때 진행 중인 함수를 중지시킨다.

또한 Promise를 리턴하는 경우 .then으로 연속적인 체이닝을 구성할 수 있다. 즉, reslove시 연속적인 .then들이 실행되고 reject 발생시 .catch가 발생한다고 볼 수 있다.

링크텍스트 --- then Method

Promise는 다음 중 하나의 상태를 가집니다.

대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
이행(fulfilled): 연산이 성공적으로 완료됨.
거부(rejected): 연산이 실패함.



( promise example )



하지만 마찬가지로 promise도 반복적으로 사용되면 지저분한 코드가 연출될 수 있다.


( promise hell example )



promise hell을 막기 위해선 return 처리를 적절하게 해주어야 한다.


( promise chaining example )



async & await을 사용해서 동기적 흐름을 연출할 수 있다.


이 메서드는 async 함수의 실행을 일시 중지하고 전달 된 Promise의 해결을 기다린 다음 async 함수의 실행을 다시 시작하고 완료후 값을 반환한다.

즉, promise와 결부되어 작용하여 실행된다.


( async & await example )

링크텍스트 ( Promise 참고 사이트 )

링크텍스트 ( Node.js 참고 사이트 )

링크텍스트 ( Node.js fs 모듈 참고 사이트 )



< Node.js fs 모듈 사용법 >

메소드 fs.readFile 은 비동기적으로 파일 내용 전체를 읽습니다. 이 메소드를 실행할 때에는

인자 세 개를 넘길 수 있습니다.


fs.readFile(path[, options], callback)

path에는 파일 이름을 인자로 넘길 수 있습니다. 네 가지 종류의 타입을 넘길 수 있지만 일반적으로 문자열의 타입으로 넘깁니다.

fs.readFile('/etc/passwd', ..., ...) --- '/etc/passwd' 파일을 불러오는 예제

[ options ]

대괄호로 감싼 두 번째 인자 options는 넣을 수도 있고, 넣지 않을 수도 있습니다.

대괄호는 선택적 인자를 의미합니다. 공식 문서에서도 인자를 세 개를 넣는 경우와, 두 개를 넣는 경우를 예제와 함께 소개하고 있습니다.

options는 객체 형태 또는 문자열로 넘길 수 있습니다. 문자열로 전달할 경우 인코딩을 넘깁니다. 인코딩은 두 번째 예제를 참고하세요. 두 번째 예제에서는 'utf8' 을 두번째 인자로 전달하는 것을 확인할 수 있습니다.

let options = {
  encoding: 'utf8', // UTF-8이라는 인코딩 방식으로 엽니다
  flag: 'r' // 읽기 위해 엽니다
}

// /etc/passed 파일을 옵션을 사용하여 읽습니다.
fs.readFile('/etc/passwd', options, ...) 
[코드] 두 번째 인자 options에는 인코딩 정보를 전달합니다.


callback --- (Function)

err -- (Error)
data -- (string) | (Buffer)


콜백 함수를 전달합니다. 파일을 읽고 난 후에 비동기적으로 실행되는 함수입니다.

콜백 함수에는 두 가지 파라미터가 존재합니다. 에러가 발생하지 않으면 err 는 null 이 되며, data 에 문자열이나 Buffer 라는 객체가 전달됩니다. data 는 파일의 내용입니다.

data 에는 문자열이나 Buffer 가 전달됩니다.

엔코딩이 명시되어 있으면 문자열, 반대의 경우에는 Buffer가 전달됩니다.

If no encoding is specified, then the raw buffer is returned. --- 공식 문서 내용
fs.readFile('test.txt', 'utf8', (err, data) => {
  if (err) {
    throw err; // 에러를 던집니다.
  }
  console.log(data);
});
[코드] 메소드 fs.readFile로 파일의 데이터를 읽어 들입니다. --- 예제



< Fetch API >


비동기 요청의 가장 대표적인 사례는 단연 네트워크 요청입니다.

네트워크를 통해 이뤄지는 요청은 그 형태가 다양하고 그 중에서 URL로 요청하는 경우가 가장 흔합니다.

URL로 요청하는 걸 가능하게 해 주는게 fetch API 입니다.

fetch API는 특정 URL로부터 정보를 받아오는 역할을 합니다. 이 과정이 비동기로 이루어지기 때문에, 경우에 따라 다소 시간이 걸릴 수 있습니다.

이렇게 시간이 소요되는 작업을 요구할 경우에는 blocking이 발생하면 안 되므로, 특정 DOM에 정보가 표시될 때까지 로딩 창을 대신 띄우는 경우도 있습니다.

let url =
  "https://~~ ";
fetch(url)
  .then((response) => response.json()) // 자체적으로 json() 메소드가 있어, 응답을 JSON 형태로 변환시켜서 다음 Promise로 전달합니다
  .then((json) => console.log(json)) // 콘솔에 json을 출력합니다
  .catch((error) => console.log(error)); // 에러가 발생한 경우, 에러를 띄웁니다
  
  --- fetch API를 사용하여 데이터를 요청




링크텍스트 --- Event Loop

profile
세상은 넓고 배울건 많다

0개의 댓글