네트워크와 통신할 준비-동기와 비동기

LSA·2022년 3월 12일
0

javascript+a

목록 보기
3/4
post-thumbnail

제가 자료를 찾아보고 읽은 것을 바탕으로 간단히 정리한 글입니다.
자세한 설명 자료는 아래에 있습니다.

이전에 filter함수에 대해 익혔을때 콜백함수의 존재로 인해 비동기동기처리에 대해 간단히 찾아보았습니다.(여기서)
하지만 생각보다 중요한 개념이어서, 다시 내용을 찾고 천천히 익혀 보기로 합니다.

통신에는 시간이 걸린다

위 자료를 보기 전까지,저는 네트워크와 통신하며 데이터를 주고받는 시간 자체가 0.0001초만에 된다고 생각했습니다. 물론 요즘 인터넷은 빠르긴 합니다만, 서버에 수천만개의 데이터 중 하나, 혹은 몇백개,수만 개의 데이터를 가져오는것은 서버라도 조금 힘이 듭니다.

데이터를 가져오는 데에는 아무리 짧다 해도 시간이 걸린다.

서버는 단지 우리 인간보다 빠르게 일을 수행할 뿐입니다.

그래서 비동기 처리가 필요하다.

한 코드의 작동이 끝날 동안 다른 코드를 먼저 실행해주는 처리방식

쉽게 말하면, 체크인 시간이 3시부터인 호텔에 갔다고 합시다.
그런데 호텔에 너무 일찍 도착해서 1시간의 여유시간이 생겼어요.
그럼 우리는 호텔 근처에서 쇼핑을 할수도 있고, 산책을 할 수도 있습니다. 그렇게 시간을 때운 후 3시에 다시 체크인을 할 수 있죠.
이것이 비동기 처리의 한 예라고 볼 수 있습니다.
(반대로 3시까지 아무것도 안하고 기다렸다가,3시가 되면 체크인을 하는 것이 동기 처리 방식)

콜백 함수의 한계

많이 보셨겠지만 콜백 함수는 간단히 함수 안의 함수입니다.

function sayYaho(say){
	console.log(say);
      setTimeout(function(){//2초 후에 해당 함수를 실행하는 setTimeout 메소드
        console.log(say)
      },2000)
    
   
}
sayYaho('야-호');
/*실행결과:
'야-호'
(2초 후)
'야-호'*/

만약 데이터 통신 후 데이터를 기다리는 시간이 2초정도라고 해봅시다.
저렇게 위에다가 콜백 함수를 넣어주면 사용자는 2초 * n배의 시간을 기다려야 할지도 모릅니다.
결국 큰일납니다.

비동기적으로 코드 짜기

그러면 함수가 멍때릴 일 없이 좀더 똑똑하게 만들려면 어떡해야 할까요.

A. Promise 사용하기

Promise는 javascript 비동기 처리에 사용되는 객체

//기본 문법
new Promise((resolve,reject){
	//여기에 뭔가 들어온다!
});

보통 서버에서 받아온 데이터를 화면에 표시할 때 사용합니다.
그전에 먼저 fetch라는 함수를 알아야 할 필요가 있습니다. (참고: https://www.daleseo.com/js-window-fetch/)
아래는 제가 위벅스 프로젝트를 실습하며 작성한 useEffectfetch를 이용해 json 데이터를 불러오는 함수입니다.

useEffect(()=>{
        fetch('data/coldBrewcoffeeList.json',{method:'GET'})
            .then(res => res.json())
            .then( data =>{
                setColdBrewCoffeeList(data);
            });    
        fetch(
            'data/brewcoffeeList.json',{method:'GET'})
            .then(res => res.json())
            .then( data =>{
                setBrewCoffeeList(data);
            }); 
    }, []);

아직은 이 함수가 정확히 어떤 식으로 굴러가는지 이해하지 않아도 됩니다.

A-1. Promise의 3가지 상태

  1. Pending (대기)
  2. Fulfilled (이행)
  3. Rejected (실패)

01. Pending

Promise를 새로 생성하면 데이터가 아무것도 들어오지 않은 대기상태입니다.

new Promise() {
  
});

02. Fulfilled

new Promise(function(resolve, reject) {
  resolve();
});

인자로 들어온 resolve를 저렇게 실행시켜주면 이행(혹은 완료) 상태가 됩니다.
이 resolve 데이터는 어떻게 출력하냐.
일단 데이터를 냉면으로 설정해봅니다.

function getColdNoodle(){//함수 getColdNoodle은 
  return new Promise(function(resolve, reject) {//새 Promise를 생성한다.
  const data = '냉면';//data 값은 냉면이다.
  resolve(data);//냉면 데이터를 resolve시켜라.
  });
}
//데이터가 잘 뽑혔어?그럼 getColdNoodle에 이걸 실행해.
getColdNoodle().then(function (resolveData){//바로 resolveData라는 인자를 받아온 함수를..
  console.log(resolvedData); // getColdNoodle에서 받아온 데이터를 콘솔로 출력,
});
//결과는 '냉면'

여기서 그럼 getColdNoodle에 이걸 실행해. 의 역할을 하는것이 then() 메소드 입니다.
물론 저렇게 데이터나 promise 객체를 받아오지 않는 일반 함수에는 별다른 힘을 못씁니다.

03. Rejected

new Promise(function(resolve, reject) {
  reject();
});

위와 같이 reject를 호출하면 실패 상태가 됩니다.
실패상태를 굳이 불러온다고요?저도 지금으로선 정확히 이해가 안가네요.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));//새로운 에러를 만들건데,'Request is failed'라는 사유의 에러이다.
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

이렇게 에러 메세지는 catch() 메소드를 이용해 불러옵니다.
아마 콘솔에 나오는 에러 메세지를 우리가 직접 커스텀할수 있다는..그런 꿈이 생겼습니다.

promise에 대한 예제는 위의 자료에서 예시 코드를 확인해봐야겠습니다.
아직 예제를 짤 정도로 이해가 안갔습니다.

B. Async 사용하기

Async는 위의 Promise를 사용한 원리와 같지만, 조금 더 심플한 문법을 사용하는 차이입니다. 이런 것을 Syntactic Sugar라고 한다네요!

function fetchData(){
  
  return new Promise((resolve,reject)=>{
    resolve('냉면'); //데이터 '냉면'을 promise 타입으로 가져온다.
  });
  
}
let data = fetchData(); //변수 data는 fetchData()함수다.
data.then(console.log);//위의 데이터가 잘 채워졌다면,console.log로 데이터를 출력해!
//결과: '냉면'

console.log(data)의 형태가 아닌것이 의아한가요?
저렇게 괄호를 생략하고 써도 결과적으로는 data가 나올 수 있습니다.
data.then(console.log) = data.then(console.log(data)) 인겁니다.
이 promise를 이용한 함수를 Async(에이싱크)로 바꿔보면..

async function fetchData(){
  
  return '냉면'; //데이터 '냉면'을 promise 타입으로 가져온다.
 
}
let data = fetchData(); //변수 data는 fetchData()함수다.
data.then(console.log);//위의 데이터가 잘 채워졌다면,console.log로 데이터를 출력해!
//결과: '냉면'

promise 함수를 따로 작성하지 않아도 결과는 같습니다.

B-1. await

await는 말그대로 기다리라는 명령어입니다.또한 이친구는 같은 a씨를 가진 async 밑에서만 동작합니다.

function delay(ms){//delay 함수는 인자 ms(밀리세컨드)를 가져온다.
  return new Promise(resolve =>{//새 promise 객체를 만드는데
    setTimeout(resolve,ms)//resolve된 데이터를 받아와서 setTimeout의 파라미터로 넣어준다.
  });
}
async function getA(){//getA 함수는 새 promise 생성
	await delay(1000);//delay 함수에 1000을 인자로 넣을건데 기다려!
    return "A";
} //>위의 delay함수를 거쳐,setTimeout('A',1000)이렇게 인자가 넣어지므로 1초 후 "A" 생성

function getB(){//getB 함수 생성
	return delay(1000)//delay 함수에 1000을 인자로 넣은 것을 리턴.그러면 setTimeout('B',1000)의 형태가 됨
    .then(()=> 'B');//위 함수가 잘 작동된다면 'B'를 리턴해!
} 

여기서 getAgetB는 문법만 다르고, 방식은 동일합니다.
그리고 A와 B를 연결하여 최종적으로 출력한다고 할때

async function callAB(){
      const callA = await getA();
      const callB = await getB();
    return `${callA}+${callB}`
  }

//promise 형식으로 쓰면 이렇게 된다
/*function callAB(){
     return getA().then(callA =>{
    	 return getB().then(callB =>`${callA}+${callB}`);
     });
  }*/

callAB()는 A의 결과와 B의 결과를 연결한 값을 리턴하게 됩니다.
출력을 하고싶으면 console.log(callAB());이렇게 써야할까요?

promise의 값에 A+B가 담긴 모습만 보게 됩니다.
async를 사용하면 어쨌든 최종적으로 반환되는 것은 promise형태이기 때문에 저걸 문자열로 출력하려면

callAB().then(console.log)

이런 형태로 써주어야 getA()의 딜레이 시간 1초,getB()의 딜레이 시간 1초를 합한 총 2초 후에 문자열 "AB"가 출력됩니다.

물론 asyncawaitnew Promise.then()을 쓰는 것에 비해 간편한 문법이지만,그렇다고 저 친구들만 쓰는 게 능사는 아닙니다....만,

await 남발하기

저렇게 async와 await을 이용하여 비동기 처리를 동기 처리하는식으로 짠다면, 문제가 생깁니다.

function delay(ms){
  return new Promise(resolve =>{
    setTimeout(resolve,ms)
  });
}
async function getA(){
	await delay(1000);
    return "A";
} 

function getB(){
	return delay(3000)//3초로 증가
    .then(()=> 'B');
} 

async function callAB(){
      const callA = await getA();
      const callB = await getB();
    return `${callA}+${callB}`
  }

위 함수를 다시 봅니다.getB()함수의 딜레이 시간이 3초로 늘었어요.
callAB()에서 'AB'를 보려면, 우리는 총 4초의 시간을 기다려야합니다.

한국인은 참지못함
저 딜레이를 조금이나마 줄여주기 위해 callAB()를 수정해봅니다.
병렬적으로 함수를 실행시키는 방법입니다.

async function callAB(){
  	const promiseA = getA();//새 promise를 바로 만든다.
    const promiseB = getB();
    const callA = await promiseA;
    const callB = await promiseB;
    return `${callA}+${callB}`
  }
//결과는..직접 확인하세요:)

그런데 코드 줄이 2줄이 더 늘어버렸네요.
저 기능을 묶어서 표현하는 방법이 없을까요?

Promise API

async function callAB(){
  	return Promise.all([getA(),getB()])
  .then(string=>string.join('+'));
  }
callAB().then(console.log)

.all([들어갈 데이터])메소드를 사용하면 됩니다!배열 안에 함수들을 차례로 줄세워서 결과를 출력합니다.
저 all이 잘 처리되면, 이제 string 인자로 들어온 getA()와 getB()의 결과를 join()메소드를 이용해 문자를 합칩니다.
이러한.all()메소드는 각 함수마다 걸리는 시간이 각기 다를 때 효율적으로 사용이 가능합니다.

다음 포스팅에는 자바스크립트 동작원리에 대해 공부하고 쓰겠습니다.(이거 이해하고 쓰는데만도 반나절이 날아갔다 합니다.)

profile
진짜 간단하게 작성한 TIL 블로그

0개의 댓글