코드스테이츠/코드스테이츠 @ 개발 복습

[코드 스테이츠] 48일차, "7주차 복습 (1) - 비동기, fetch"

Je-chan 2021. 9. 4. 23:19


[ 오늘의 TODO ]

  1. 코드 스테이츠) 월~수 내용 복습
  2. // 비동기 (진짜 빡세네)
  3. // Node.js 모듈 사용
  4. // fetch
  5. 패스트 캠퍼스) 인강 3개 이상 듣기 // optional
  6. 스터디 그룹) 프로그래머스 문제 풀기
  7. // 멀리 뛰기
  8. // Weekly 2주차
  9. 생활) 물 1L 이상 마시기
  10. 생활) 수-토-일 운동

 


[ 오늘의 복습 ]

1. 비동기란?

출처: https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1/

1) 동기(Synchronous)

  비동기의 반대는 동기다. 우리가 사용하는 일반적인 함수들은 동기적으로 작동한다. 동기적으로 진행한다는 의미는 이전 것의 완료 시점과 다음 것의 시작 시점이 같은 상황을 의미한다.

  내가 4년 동안 일했던 아웃백 스테이크 하우스의 사정을 예시로 들어보자. 아웃백에서는 거의 동시에 온 손님이라도 먼저 발을 들인 손님 순서대로 자리를 안내한다. 손님마다 주문을 결정하는 시간이 다르다. 자리에 앉아서 메뉴판을 보고 주문을 결정하는 것을 일종의 완료 시점으로 본다면, 동기적으로 일한다는 건 메뉴판 한 개로 먼저 들어온 손님이 주문을 다 끝낸 후, 다음에 들어온 손님에게 메뉴판을 건네주고 주문을 시작한다는 얘기다. 즉, 먼저 온 손님은 메뉴판을 보고 주문을 결정하는데 20분이 걸리고, 나중에 온 손님은 10분이 걸린다고 하자. 그러면 먼저 온 손님에게 메뉴판을 주고 20분 뒤에 주문이 완료되고 그다음에 온 손님에게 메뉴판을 건네주어 10분 후에 주문을 완료하는 것이다. 이렇게 되면, 아웃백은 지금까지 온 손님의 모든 주문을 받기까지 총 30분이 소요되고, 뒤에 오신 손님에게는 다시 오고 싶지 않은 가게의 이미지를 주게 된다.

  일반적인 함수의 경우, 그 주문 완료하는 시간이 굉장히 짧기 때문에 동기적이어도 상관이 없다. 하지만, 주문 완료하는 시간이 오래 걸리는 작업들이 있다. 그런 작업을 할 때, 동기적으로 작업한다면 뒤에 빠르게 주문을 완료할 손님이 있음에도 불구하고 오래 기다려야 하듯이 큰 불편을 안겨준다. 이럴 때 비동기적인 처리를 진행한다.

2) 비동기(Asynchronus)

  이게 우리가 직관적으로 받아들이는 방식이다. 손님은 순차적으로 받지만 받자마자 바로 메뉴판을 가져다준다. 그리고 주문이 결정되면 바로 주문 완료를 하는 것이다. 앞 선 예시를 가져오면 손님이 거의 동시에 들어오는데, 그 두 손님에게 들어오자마자 메뉴판을 가져다주는 것이다. 그리고 10분 뒤에 순서로 따지면 늦게 온 두 번째 손님이라 할지라도 먼저 주문을 완료하고 20분이 됐을 때에 먼저 온 손님의 주문을 완료한다. 이렇게 되면, 아웃백은 지금까지 온 손님의 모든 주문을 받기까지 총 20분이 소요됐고, 뒤에 오신 손님에게도 그다지 불편을 끼치지 않았을 것이다.

  이렇게 비동기는 주문 완료가 굉장히 늦게 될 때 시행하면 적절하다. 대표적인 예시로는 다음 세 가지 경우가 있다.

  1. 백그라운드 실행, 로딩 창 작업
  2. 서버를 요청하고 응답을 기다리는 작업
  3. 큰 용량의 파일을 로딩하는 작업

 

2-1) 비동기에 대한 추가 예시와 사용하는 이유

  비동기에 대해 좀 더 쉽게 설명해보자. 동기적으로 일한다면 인과적으로 어떤 일을 해야지 다음 일을 할 수 있는 거고 비동기적으로 일한다면 여러 일을 동시에 할 수 있는 거다.

  JE는 사무직 일을 하고 있다. 이번에는 해야 할 업무는 재무팀에게서 자료를 3개 받아서 그걸 PPT로 만드는 작업이다. 동기적으로 일하는 건 다음과 같은 경우다. JE는 한 번에 일을 하나밖에 못하는 바보라고 하자. JE가 일을 하기 위해 재무팀에게 자료 1을 만들어 달라고 요청한다. 재무팀이 부랴부랴 자료 1을 만들어서 건네받으면 자료 1에 대한 PPT를 만든다. 다 만든 후에 자료 2를 만들어 달라고 요청한다. 그러면 또 재무팀이 부랴부랴 자료 2를 만드는데 그때 동안 JE는 할 일이 없기에 빈둥거린다. 자료 2가 만들어지면 그때부터 다시 일을 시작해서 자료 2에 대한 PPT를 만든다. 다 만들면 자료 3을 만들어 달라고 요청한다. JE는 다시 그 자료 3을 받기 전까지는 할 일이 없으므로 빈둥거린다. 재무팀이 자료 3을 다 만들어 주면 그제야 다시 일을 시작해 자료 3에 대한 PPT를 만든다. 그러고 나서 PPT 디자인도 바꾸고 다른 인사말이나 추가적인 내용을 넣고 디자인을 꾸며서 일을 마친다. 그리고 상사에게 일 너무 느리게 한다며 욕을 엄청 먹는다.

  JE는 그날 이후로 각성했다. 그다음 날 똑같은 일을 하게 됐는데 이번에는 JE가 비동기적으로 일을 처리하고자 한다. JE는 재무팀에게 시간이 없으니 바로 자료 1, 2, 3을 요청한다. 말한 순서는 1, 2, 3이지만 거의 동시적으로 요청을 한 거나 다름이 없다. 재무팀이 자료 1, 2, 3 을 만드는 동안 JE는 PPT 템플릿을 디자인하고 구성하고 만들어낸다. PPT 디자인과 재무팀 자료 1, 2, 3만을 제외한 내용들을 다 넣었을 즈음에 재무팀에게서 자료 1, 2, 3이 온다. 이 자료는 재무팀의 능력에 따라서 1, 2, 3 순서대로 올 수도 있고 2, 1, 3의 순서대로 올 수도 있다. 어쨌든 이렇게 받은 자료들을 바탕으로 빠르게 PPT 형식에 맞게 바꾼 후에 PPT 에 내용을 넣어서 완전한 PPT 를 만든다. 그리고 상사에게 이제 좀 일을 한다며 칭찬을 받는다.

  이 예시를 통해서 알 수 있는 건 동기적으로 일할 때는 재무팀에서 자료를 받기 전까지의 공백이 발생한다는 점이다. 요청을 받는데 걸리는 시간 동안 아무것도 안 하기 때문에 상사에게 욕을 먹는다. 컴퓨터의 경우 웬만한 일들을 순차적으로 진행한다지만 거의 동시적으로 작업하는 것과 가깝게 일처리 한다. 하지만, 위의 비동기를 사용하는 대표적인 예시처럼 굉장히 오래 걸리는 작업이 분명히 존재한다. 동기적으로 실행하게 되면 오래 걸리는 작업들로 인해 Block 이 발생한다. Block이란, 다른 작업을 해야 하는데 이전 작업의 시간이 너무 오래 걸리는 바람에 다음 작업을 할 수 없는 경우를 의미한다. 반면에 비동기로 작업하면 오래 걸리는 작업들이 있다 한들 Non-block 해서 먼저 할 수 있는 작업들은 다 작업하고(이런 작업들은 아까 전의 말처럼 거의 동시적으로 작업이 끝난다) 오래 걸리는 작업이 끝나면 그것을 가져와서 사용한다. 보통 서버 요청이라고 할 때, 일단 서버에 데이터 좀 달라고 부탁을 하고 내 할 일을 다 한 후(비동기로 작업하지 않는 일들은 동시적으로 끝나기에 동기가 거의 다 끝난 다음에 비동기 작업을 처리하는 것처럼 보이게 된다) 데이터 받기를 기다렸다가 데이터가 오면 다시 그것을 갖고 내가 원하는 모양이 되게끔 작업을 해서 사용한다.

2-2) 비동기 대표적인 예시

  1. DOM Element 이벤트 핸들러
    - onclick, onchange 등 마우스, 키보드의 입력으로 콜백 함수를 실행하는 경우
    - 페이지 로딩

  2. 타이머
    - 타이머 API (setTimeout, setInterval)
    - 애니메이션 API

  3. 서버에 자원 요청 및 응답
    - fetchAPI
    - AJAX 등등

 

2. 비동기끼리 순서 제어하기

  비동기가 좋은 것은 알겠다. 시간이 오래 걸리는 작업들은 일단 해달라고 던져 놓고 내 할 일을 다 한 후에 해준 거 받아서 그거를 갖고 해야 했던 일을 하면 되기 때문이다. 그러나 비동기의 가장 큰 단점은 언제 끝날지 모른다는 점이다. 만약, 비동기로 처리해야 하는 일이 여러 개가 있다면 어떻게 될까? 비동기가 서로 동일한 위치에서 서로에게 영향을 끼치지 않는다면 문제 될 건 없겠지만 비동기 사이에서 가장 먼저 일 처리해줬으면 하는 우선순위가 존재한다면? 그리고 내가 작업해야 하는 것들이 비동기 함수들의 순서에 맞게 작업해야 하는 일이라면? 아까 전 JE 의 PPT 만드는 작업이 "반드시", "무조건" 자료 1, 2, 3의 순서대로 PPT에 넣어야만 하는 피치 못할 사정이 있었다고 하면 재무팀은 2, 1, 3 의 순서로 자료를 넘겨줬다고 했을 때 JE 가 먼저 온 자료 2가 자료 1인줄 알고 자료 2를 자료 1이라고 PPT에 썼을 수도 있다.

  이런 걸 방지하기 위해, 우리는 비동기끼리도 순서를 정해줄 수 있게 만들어 준다. 예시를 들자면, JE 가 재무팀에게 "반드시 자료 1 먼저 해주시고 그다음에 자료 2, 그다음에 자료 3 순서대로 넘겨주세요.라고 말하는 것과 동일하다."

  우리는 서버에 , "오늘 시간 정보", "오늘 날씨 정보", "동영상 정보"를 요청한다고 가정해보자. 순서를 정해주지 않았을 때의 코드와 출력은 다음과 같을 것이다.

const takeData = (data) => { 
  setTimeout( () => { 
    console.log(data) // 데이터를 가져왔다는 의미 
  }, Math.floor(Math.random()*100) + 1 // 랜덤한 시간에 함수를 실행 
  ) } 

const data = () => { 
  takeData("오늘의 시간") 
  takeData("오늘의 날씨") 
  takeData("동영상 정보") 
} 

data()


여기서 console.log(data) 는 데이터를 가져왔다는 것을 의미한다. 이걸 매번 새로 고침 할 때마다 콘솔창은 다음의 변화를 나타낸다.

  이렇듯 내가 요청한 순서대로 데이터를 받아오는 게 아니라 랜덤 하게 받아온다. 이제 순서대로 데이터를 가져와달라고 요청을 해보자

1) Callback으로 제어하기

 

const takeData = (data, callback) => { 
  setTimeout( () => { 
    console.log(data) 
    callback() 
  }, Math.floor(Math.random()*100) + 1 
  ) } 


const data = () => { 
  takeData('오늘의 시간', () => { 
    takeData('오늘의 날씨', () => { 
      takeData('동영상 정보', () => {
      }) 
    }) 
  }) 
} 


data()


이건 콜백으로 호출을 한다. setTimeout 안의 상황을 보면 일단 console.log(data)를 한 후(이후 데이터를 가져온 후라고 언급하겠음) 그다음 함수를 실행한다. 시간, 날씨, 동영상 정보에 대한 내용을 불러들이고 있지만 시간의 데이터를 가져온 후에 날씨를 가져오는 함수를 콜백 함수로 실행한다. 그렇게 콜백으로 실행한 날씨의 데이터를 가져오는 함수가 데이터를 가져오면 그때 콜백 함수로 넣은 동영상 데이터를 가져오는 함수를 실행한다. 이런 식으로 콜백을 통해서 순차적으로 데이터를 가져올 수 있도록 한다.


이렇게 콜백으로 요청해도 좋다. 하지만, 실무에서 복잡한 연산을 구현하게 될 때 콜백을 함부로 남발하면 Callback Hell, 일명 콜백 지옥이 실현된다.

콜백 지옥의 극단적인 예. 출처: https://ichi.pro/ko/javascript-yagsog-ilan-mueos-ibnikka-188594296949114



2) Promise로 제어하기

  이 문제를 해결하고자 ES6 이후로 새로 문법이 생겼으니 그게 바로 Promise 다. Promise 는 일종의 class 라서 new 와 함께 묶여 인스턴스를 생성한다. Promise 에 내장된 메소드로는 대표적으로 .then 과 .catch 등이 있다.

2-1) Promise의 특징들

  Promise의 큰 특징부터 잡고 가자.
  
  첫 번 째, Promise는 new 로 만들자마자 실행된다.

// 함수의 경우 

const functionTest = () => { 
  console.log('hi') 
} 

functionTest() // 함수는 이렇게 꼭 호출을 해야 콘솔창에 hi 가 찍힘 


// Promise의 경우 

const promise = new Promise ((resolve, reject) => { 
  console.log('hi') 
}) 

// 이렇게 선언만 했을 뿐인데 console 창에 hi가 찍힌다. 
// 즉, Promise 가 선언되면 바로 Promise 의 내용이 실행된다는 것.


  두 번째, method 인 then 과 catch 는 Promise를 반환한다. 지금은 설명하기엔 복잡하니 조금 있다가 사례를 들 때 이게 무슨 말인지 설명하도록 하자

  세 번째, Promise는 크게 Producer와 Consumer 로 나뉜다. Producer는 위에 처럼 Promise를 생성하는 구간이고, Consumer는 생성하자마자 Promise는 실행이 되는데 그 실행된 값을 받아서 사용할 구간을 의미한다. 이것도 조금 있다가 사례에서 설명하도록 하겠다.

일. 단은 큰 특징들만 설명했고 아래서부터는 세부적인 특징들을 설명하도록 한다.

2-2) Promise로 순서 제어하기

 

const takeData = (data) => { 
  return new Promise((resolve, reject) => { 
    setTimeout( () => { 
      console.log(data) 
      resolve(data) 
    }, Math.floor(Math.random()*100) + 1 ) 
  }) 
} 

// [첫 번째 사용법] 
// 아래 console.log(`{data}를 받아왔다는 걸 표기합니다.`)는 이후 설명을 위해 임의적으로 넣은 것뿐 

const data = () => { 
  takeData('오늘의 시간') 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
    return takeData('오늘의 날씨') 
  }) 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
    return takeData('동영상 정보') 
  }) 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
  }) } 


data() 




// [두 번째 사용법] 

const data = () => { 
  takeData('오늘의 시간') 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
    takeData('오늘의 날씨')
      .then( data => { 
        console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
        takeData('동영상 정보')
          .then( data => { 
            console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
          }) 
      }) 
  }) 
} 


data()  


// 이 두가지는 엄연히 다르다 (밑에서 설명함) 

// 첫 번째 사용법은 Promise Chaining을 해주고 있지만 (이후에 설명함) 
// 두 번째 사용법은 Promise Hell을 만들어주고 있다. 

// 그렇기에 첫 번째 사용법이 권장된다. (안 그럼 콜백 지옥과 동일하기 때문)


  이렇게 적어주면 순서대로 데이터를 받아들여 다음과 같은 결과를 만들어낸다.

(여기서는 reject, catch 에 대한 설명은 제외합니다. 패캠 인강 카테고리의 비동기 함수 (Promise) 에서 자세히 다루고 있으니 확인해주세요)



  Promise는 현재 데이터를 받아오는 용도로 사용하고 있다. 만약 어떤 에러 없이 정상적으로 데이터를 받아 오면 resolve 라는 콜백 함수를 호출한다. 이때, 보통은 resolve 인자에 정상적으로 받아온 데이터를 넣는다. resolve에 넣은 인자는 Consumer 에서 .then 메소드로 넘어간다. 그러면 그 인자는 then 메소드를 통해서 console 창에 찍어주는 용도로 사용했다. 그다음에 return을 해주는 데, 그 return 한 값은 다른 데이터를 불러오는 Promise 인스턴스 값을 해줬다. 이때, return 될 값은 다름 아닌 resolve 된 값일 것이다. 우리는 아까 전에 then 메소드가 Promise 를 반환한다고 했다. 이는 그 안에 then 이라는 메소드가 존재한다는 것을 의미한다. 마치 배열의 메소드인 map 이 새로운 배열을 생성하기에 연달아서 map을 또 사용해줄 수 있듯이 then 다음에 연달아서 또 then 을 사용해줄 수 있는 것이다.

let arr = [1, 2, 3] 

arr 
.map(el => el * 2) // 여기서 map은 arr 란 배열의 메소드고 생성된 arr는 [2, 4, 6] 
.map(el => el * 2) // 여기서 map은 [2,4,6] 배열의 메소드고 생성된 arr는 [4, 8, 12] 

// 이와 똑같이 .then 도 이전의 .then에서 생성된 Promise의 메소드인 것

 

  위의 코드를 통해서 우리가 얻을 수 있는 점은 두 번째 then 은 첫 번째 then의 결과로 생성된 Promise 의 메소드라는 것을 의미하고, 첫 번째 then의 결과로 생성된 Promise는 다름 아닌, return 된 takeData(인자) 의 값이다. 만약에 return을 안 해주거나 인자가 then의 인자인 data가 된다면 어떤 일이 벌어질까?

// [1] 

const data = () => { 
  takeData('오늘의 시간') 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
  }) 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
  }) 
  .then( data => { 
    console.log(`${data}를 받아왔다는 걸 표기합니다.`) 
  }) 
}


이런 상황이라면 then 으로 생성된 Promise 가 존재하기에 두 번째 then을 체이닝(연달아서 사용)할 수 있지만, 두 번째 then 부터는 어떤 인자를 받는지 전혀 내용이 없으므로 undefined 가 된 결과를 만들어 낸다.

 

// 설명이 필요하기에 takeData 를 가져왔다. 

const takeData = (data) => { 
  return new Promise((resolve, reject) => { 
    setTimeout( () => { 
      console.log(data) 
      resolve(data) 
    }, Math.floor(Math.random()*100) + 1 ) 
  }) 
} 


// [2] 


const data = () => { 
  takeData('오늘의 시간') 
  .then( data1 => { 
    console.log(`${data1}를 받아왔다는 걸 표기합니다.`) 
    return takeData(data1) // a 
  }) 
  .then( data2 => { 
    console.log(`${data2}를 받아왔다는 걸 표기합니다.`) 
    return data2 // b 
  }) 
  .then( data3 => { 
    console.log(`${data3}를 받아왔다는 걸 표기합니다.`) 
  }) 
} 


data()


  then 은 Promise 를 생성하지만 그게 takeData 는 아니다. map과 마찬가지로 새로운 Promise 객체를 생성하는 것이다. 그걸 분명히 짚고 넘어가자. takeData( ) 는 그 함수 안에서 만들어진 Promise의 인스턴스를 return 한다. 그 Promise 의 인스턴스는 console.log(data)를 한다. 이걸로 인해 콘솔창에 data의 내용이 출력될 것이다. 그러면 a에서 takeData 안에 있는 resolve(data)는 어디로 갈까? 아무 데도 안 간다! 왜냐하면 a의 takeData(data1) 뒤에 .then 이 없기 때문이다. b를 포함한 두 번째 then 은 a를 포함한 첫 번째 then 으로 생성한 새로운 Promise 객체의 메소드이지 takeData(data1) 의 메소드로서의 then 이 아니다! (이게 내가 맨 위의 정상적인 코드에서 [첫 번째 사용법] 과 [두 번째 사용법] 이 결과는 같아도 엄연히 다르다고 한 이유다)

  일단, a 에서 리턴해주고 있는 것은 takeData(data1) 다. 그렇다면 그게 정확히 어떤 값을 가리킬까? console.log(takeData("hello")) 를 해주니 아래의 사진처럼 나왔다. 저 결과를 살펴보면 "hello" 가 나오는데 이건 우리가 인자로 넣어준 값이 아니라 resolve 안에 넣어준 값을 의미한다. 그렇기에 return 으로 받아오는 건 resolve 안에 들어있는 내용이 된다.


  그러면 이제 주석으로 a 처리가 된 부분은 console.log(data1)을 하고 data1 값을 반환하는 내용이라고 할 수 있다. data1 은 then 이 인자로 받아온 것을 가리키는데 그 값은 takeData('오늘의 시간') 으로 해서 resolve 된 값이므로, '오늘의 시간'이 된다. a 를 포함하는 첫 번째 then은 Promise 를 생성하는데 return 하는 값이 '오늘의 시간'인 Promise 객체를 생성한 것이다.

  그럼 이 Promise 를 연결한 두 번째 then 은 인자 data2는 무엇을 인자로 받아올까? 앞에 있던 Promise의 return 값인 '오늘의 시간' 이 된다. 그러면 b는 단순히 return data2 를 해주고 있으므로 두 번째 then 은 '오늘의 시간' 을 리턴하는 Promise 객체를 생성한 것이다. 여기서 첫 번째 then 과의 차이점은 Promise 를 리턴했느냐 일반적인 데이터를 리턴했느냐의 차이일 것이고 보이는 결과물로써는 Promise를 실행시켜 console.log 를 찍고 resolve 안에 있는 data 값을 리턴했느냐 와 그냥 일반적으로 data 값을 리턴했느냐로 구분할 수 있을 것 같다.

  마지막 data3 는 return 된 값인 '오늘의 시간' 이 될 것이다. 따라서 위 [2] 를 콘솔창에 찍으면


  이렇게 될 것이다.

  Promise 와 resolve, then에 대한 자세한 이해를 위해서 몇 가지 에러 사항을 연출했다. 요약하자면, 데이터가 Promise 에 들어갔을 때 오류가 없으면 resolve 라는 콜백 함수의 인자로 받은 데이터를 넣고, 그 Promise 인스턴스를 return 하는 함수에 then 이라는 메소드를 사용하면 resolve 안에 넣은 인자가 then 으로 넘어간다는 것이다. 그리고 그 then 은 새로운 Promise 인스턴스를 생성하기 배열 메소드 체이닝처럼 Promise Chaining 가능해진다는 점이다.

Promise Chaining 팁

1. then 으로 넘어온 인자가 하나뿐이고, 그 인자를 받아 구현하는 함수의 값(혹은 일반 로직의 결과)을 다음 then의 인자로 넘겨주는 거라면 return 을 쓰지 않아도 된다.

const wish = (asynchronous) => { 
  return new Promise((resolve, reject) => { 
    setTimeout( () => { 
      resolve(`(${asynchronous} 잘하고 싶다)`) 
    }, Math.floor(Math.random()*100) + 1 
    ) 
  }) 
} 


const I = () => { 
  wish('비동기') 
  .then(data => wish(data)) 
  .then(data => wish(data)) 
  .then(data => console.log(data)) 
} 


// 위의 내용은 아래와 동일함 

// const I = () => { 
// wish('비동기') 
// .then(data => {return wish(data)}) 
// .then(data => {return wish(data)}) 
// .then(data => console.log(data)) 
// } 


I() 

// 결과 
// (((비동기 잘하고 싶다) 잘하고 싶다) 잘하고 싶다)


2. then으로 넘어온 인자가 하나고, then 안에서 그 인자를 받아서 사용하는 함수가 단 하나만 있다면 데이터 인자 표시 없이 그냥 함수명만 적으면 된다.

const I = () => { 
  wish('비동기') 
  .then(wish) 
  .then(wish) 
  .then(console.log) 
} 

I () 

// 위의 것과 동일한 결과를 얻을 수 있다. 
// (((비동기 잘하고 싶다) 잘하고 싶다) 잘하고 싶다)


이전에 배운 큰 특징들과 팁으로 알려준 1, 2를 묶어서 다른 예시를 보여주자면

const calculate = new Promise ((resolve, reject) => { 
  setTimeout(() => resolve(3), Math.floor(Math.random()*100) + 1 ) 
}) 

const multiple = (num) => num * 3 


calculate // 선언을 하자마자 실행됨. 호출 ()이 필요 없음 
.then(num => num + 2) 
.then(multiple) 
.then(console.log) 


// 결과 
// 15 

// 비동기지만, 순서대로 작동해서 계산 순서도 순서대로 된다 

// 선언을 하자마자 실행되는 걸 방지하기 위해 
// 이전에 계속 해온 것처럼 변수에 new Promise 를 바로 할당하지 않고 
// 아래처럼 함수를 생성해 그 return 값으로 new Promise 로 하는 방식을 사용한다. 

// const calculate = () => { 
// return new Promise ((resolve, reject) => { 
// setTimeout(() => resolve(3), Math.floor(Math.random()*100) + 1 ) 
// }) 
// }

 

 

3) async, await로 제어하기

노드의 버전이 높다면 async, await 를 사용하면 된다.

const takeData = (data) => { 
  return new Promise((resolve, reject) => { 
    setTimeout( () => { 
      console.log(data) 
      resolve(data) 
    }, Math.floor(Math.random()*100) + 1 ) 
  }) 
} 


const data = async () => { 
  await takeData('오늘의 시간') 
  await takeData('오늘의 날씨') 
  await takeData('동영상 정보') 
} 


data()


  함수 앞에 async 키워드를 붙인다 함수 선언형이라면 function 앞에 async를 쓴다. 그리고, 그 안에 들어있는 비동기 함수들 앞에 await 을 붙이면 이전에 작성된 비동기 함수가 끝날 때까지 말 그대로 await, 기다린다. 이 표현 방식은 우리가 일반적으로 작성하는 동기 함수의 방식과 유사하게 작동하기에 가독성이 좋다. 단 async 안에 있기에 이 안에 있는 내용이 비동기 함수임을 인지해야 한다. 비동기 함수들 사이에서 순서를 정한 것뿐이다.



3. Node.js 모듈 사용하기

  node.js의 정의는 "비동기 이벤트 기반 자바스크립트 런타임"이다. Node.js 는 로컬 환경에서 자바스크립트를 실행할 수 있는 자바스크립트 런타임이다. 그렇기에 브라우저에서 불가능한 몇 가지 기능을 해낼 수 있다.

  모듈이란 어떤 기능을 조립할 수 있는 형태로 만든 부분을 의미한다. 편리한 기능이 많이 있어 내가 원할 때에 이런 모듈들을 하나씩 가져와서 사용할 수 있다.

https://nodejs.org/dist/latest-v14.x/docs/api/

 

Index | Node.js v14.17.6 Documentation

 

nodejs.org


  위 사이트에서 사용할 수 있는 모듈들을 확인할 수 있다. 모듈은 아는 만큼 쓸 수 있는 것이기에 모듈에 대한 공부는 따로 해야 한다.

  만약 기본으로 내장돼 있는 fs 라는 모듈을 사용하고 싶을 때는 require라는 내장 함수를 사용하면 된다. require 안에는 문자열로 작성한다.

const fs = require('fs') // 파일 시스템 모듈을 불러온다


  만약 node.js 에서 기본적으로 내장하고 있지 않는 모듈이라고 하면 그것을 다운로드하기 위해 `npm install [모듈명]` 으로 먼저 설치를 해야 한다. 설치가 완료되면 require를 사용한다

const _ = require("[모듈명]")



4. fetch

1) fetch란?

fetch는 비동기 요청의 가장 대표적 사례다. fetch는 URL을 사용해 네트워크를 요청한다. 보통 네트워크 요청을 할 때 URL로 많이 한다. 그렇게 URL을 통해서 네트워크 요청이 가능하도록 해주는 API 가 fetch API 다.

fetch API는 특정 URL 로부터 정보를 받아 오고 DOM 엘리먼트를 업데이트한다. 이 과정이 비동기로 이뤄지기에 시간이 오래 걸릴 수 있다. 특정 DOM 에 정보가 표시될 때까지 로딩 창을 돌리는 경우도 있다.

2) fetch 사용법

fetch(url) // url을 통해 API 정보를 가져온다. 
.then((response) => response.json()) 
  // fetch로 막 부르면 불러와진 데이터는 response 상태, 응답이 됐다고 하는 상태다 
  // response에는 자체적으로 json() 이라는 메소드가 있다. 
  // json () 는 데이터를 JSON 파일로 변환시켜준다 (JSON.parse와 유사) 
  // response를 json() 메소드를 통해 JSON 형태로 변환시킨다 
  // then은 새로운 Promise 객체를 생성한다고 했다. 
  // Promise Chaining을 할 수 있다. 
  // 다음 then에 넘겨줄 인자는 response.json()로 JSON화 된 데이터다 
.then((json) => console.log(json)) 
  // 콘솔에 json을 출력한다 
.catch((error) => console.log(error)); 
  // 에러가 발생한 경우, 에러를 띄운다