S2 Unit 3. JS/Node 비동기

나현·2022년 9월 23일
1

학습일지

목록 보기
20/53
post-thumbnail

💡 이번에 배운 내용

  • Section2.
    서버와 통신이 가능한 구조적인 Web App을 만들 수 있다.
  • Unit 3. Javascript JS/Node 비동기: 비동기와 관련있는 callback, promise, async/await에 대해 학습하고, Node.js와 관련있는 fs 모듈에 대해 학습한다.

느낀점

이제 까지 학습한 내용이 탱탱볼이었다면 이번에는 거의 볼링공 수준이다. 자칫 잘못 받았다간 온 몸 가득 커다란 충격을 입을 뻔한 학습내용이었다.
이번에 배운 내용들은 실습, 과제를 하면서 개념 정리가 많이 되었다. 그러나 네트워크 요청 등 비동기적 상황을 만들어야 실습이 제대로 되기 때문에 실습하기가 쉽지않았다.
특히 나는 Promise를 활용하는 예제와 과제가 제일 어려웠다. 간단한 예제는 괜찮았지만 다른 함수와 함께 활용하거나 노드의 fs모듈, fetch API 등과 함께 사용하려니 더 헷갈리고 어려웠다. 감도 오지 않아 페어의 도움을 받기도 했고, 콘솔에 일일이 찍어가면서 차근차근 값을 확인하느라 🎖콘솔좌 의 칭호를 얻었다.
그렇게 부딪혀가며 학습하다보니 좀 나아졌다. MDN 문서만 읽었을 때는 번역투의 문장들이 날 더 헷갈리게 했다. MDN은 한글 번역이 더 어렵게 느껴진다
비록 내용이 완벽하진 않지만 3일간 열심히 실습하며 이번 포스트를 작성했다.
이제는 포스트 작성하고 과제도 다시 살펴보고 복습도 하고... 슬슬 몸이 10개라도 모지라기 시작한다. 🫠 일단은 계속 해본다!


키워드

비동기, 동기, Promise, async/await, setTimeout, Node.js, 모듈(Module), fetch API


학습내용

이번 챕터를 학습하기 전에 고차함수에 대해 잘 알고 있어야 한다.
🔗 고차함수 포스트 바로가기

Ch1. 비동기(Asynchronous) (중요)

비동기는 동기(synchronous)의 반대말로 이를 이해하기 위해서 동기적이라는 개념을 알아야 한다. 프로그래밍에서 한 작업이 실행되는 동안 다른 작업은 실행하지 못하도록 막는 것을 blocking이라고 한다. 이 때 다른 작업이 시작되는 시점은 앞선 작업이 끝나는 시점인데, 이를 동기적(synchronous)이라고 한다.
비동기는 이 동기의 반대말로, 앞선 작업과 상관없이 다른 작업을 시작할 수 있음을 의미한다.
웹 개발에서 이 비동기는 중요하다. 사용자가 서버에서 데이터를 받아오는 동안 웹 페이지를 탐색하거나, 백그라운드의 데이터가 로딩되는 동안 웹 페이지를 보는 데 방해받지 않는 일들은 모두 비동기이기 때문에 가능한 일이다. 만약 작업이 동기적으로 이뤄진다면 웹페이지를 사용하는 데 걸리는 시간도 길어지고 여러 일을 효율적으로 처리할 수 없게 된다.

  • 비동기 예시 및 사례
    • DOM Element 이벤트 핸들러: 입력, 페이지 로딩 등(click, DOMContentLoaded 등)
    • 타이머 API: setTimeout 등 -> 찾아보기
    • 애니메이션 API: requestAnimationFrame -> 찾아보기
    • fetch API : 서버에 자원 요청
    • AJAX -> 찾아보기

비동기로 데이터를 처리하면 작업에 걸리는 시간에 따라 작업이 랜덤으로 끝나게 된다. 이런 경우 만약 순서를 제어해야할 때 사용하는 방법으로 callback, promise 등이 있다.

1) 비동기의 순서를 제어하는 방법 - callback

만약 함수가 비동기적으로 실행될 때, 순차적으로 작업을 실행하고 싶을 경우 콜백함수를 중첩으로 사용한다.

//playNumber 함수는 임의의 숫자 num, (콜백)함수를 인자로 받아 숫자를 출력한다.
//숫자를 출력하는 시간은 매번 랜덤이다.
function playNumber(num, callback){
  setTimeout(
    function(){
      console.log(num);
      if(callback!==undefined) callback();
  	},
  	Math.ceil(Math.random()*100)
  );
}

//비동기적 실행 - playNumber함수가 차례대로 실행되지 않는다.
function playAsync(){
  playNumber(1);
  playNumber(2);
  playNumber(3);
}
//동기적 실행 - 콜백함수 이용. playNumber함수가 차례대로(전달인자1,2,3 순으로) 실행된다.
function playSync(){
  playNumber(1, ()=>{
    playNumber(2, ()=>{
      playNumber(3, ()=>{});
    });
  });
}

위의 예제처럼 콜백함수를 사용하면 콜백함수의 수가 많아질수록 중첩도 많아져 가독성이 떨어진다. 이를 다른말로 'callback Hell'이라고도 한다.

2) 비동기의 순서를 제어하는 방법 - promise

promise는 비동기 메서드를 동기적 메서드로 사용할 수 있도록 결과 값, 실패 등을 연결하여 처리한다. 그러나 최종 값을 반환하는 것이 아니라 이름 그대로 '미래 시점에 값을 처리하겠다는 약속'을 반환한다.

🔗 참고: javascript MDN - Promise

프로미스에는 3가지 상태가 있다. 대기, 이행, 거부이며 대기를 벗어나 이행, 거부된다면 처리되었다고 한다.

  • Promise의 상태 3가지
    • 대기(pending): 초기 상태(이행, 거부 없음).
    • 이행(fulfilled): 성공. 연산이 성공적으로 완료됨.
    • 거부(rejected): 실패. 연산이 실패함.

아래는 promise를 사용한 예제이며, callback Hell을 해결할 수 있는 방법이다.

//playNumber 함수는 임의의 숫자 num, (콜백)함수를 인자로 받아 숫자를 출력한다.
//숫자를 출력하는 시간은 매번 랜덤이다.
function playNumber(num, callback){
  //new 키워드를 사용해 Promise를 생성하고 이를 리턴한다.
  //실행되지 않을 경우를 대비해 reject를 사용한다.
  return new Promise((resolve, reject)=>{ 
    setTimeout(
      function(){
        console.log(num);
        resolve(); //함수를 실행한다.
  	  },
  	  Math.ceil(Math.random()*100)
    );
  });
}

//promise 실행
function playPromise(){
  //Promise.prototype.then은 Promise를 반환하며 아래를 promise chain 이라고 한다.
  playNumber(1)
  .then(()=>{ //promise를 사용할 때 then 키워드를 사용한다.
    return playNumber(2); 
    //콜백함수 hell을 해결하려면 return을 해야한다. 안 그러면 또 중첩해야 한다.
  })
  .then(()=>{
    return playNumber(3);
  });
}

promise를 사용하기 위해선 함수를 정의할 때 new키워드로 promise를 생성하여 리턴하고, 실행할 때는 .then으로 이어서 사용할 수 있다.

  • 인스턴스 메서드 : Promise.prototype.then()
    then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수(성공, 실패)를 인수로 받는다. 위의 예제에서 resolve는 Promise가 성공적으로 실행되었을 때, reject는 실패했을 때 사용된다.
    예제의 설명처럼 Promise.prototype.then은 promise chain이 가능하다.

🔗 참고: javascript MDN - Promise.prototype.then()

  • 인스턴스 메서드 : Promise.prototype.catch()
    Promise를 리턴하며 에러가 난 경우 실행되는 메서드이다.
    Promise.prototype.then처럼 메서드 체이닝이 가능하다.
    Promise.prototype.then의 두번째 전달인자에 에러가 났을 경우의 콜백 함수를 입력하면 catch와 동일한 결과를 얻는다.

🔗 참고: javascript MDN - Promise.prototype.catch()

프로미스를 한꺼번에 사용하는 메서드도 있다.

  • Promise.all(arr)
    • 이행되었을 경우 반환값은 매개변수에 주어진 요소를 담은 배열이다.
      변수에 Promise를 할당하여 전달인자가 될 배열의 요소로 작성할 수 있다.
      프로미스가 아닌 값을 요소로 할 수도 있다.
    • 빈 객체를 전달했을 경우, 동기적으로 이미 이행한 프로미스를 반환한다.
    • 프로미스가 없거나 이미 이행된 프로미스를 전달했을 경우, 비동기적으로 이행하는 프로미스를 반환한다.
    • 주어진 프로미스 중 하나라도 거부되면, 거부된다.

🔗 참고: javascript MDN - Promise.all()

3) 비동기의 순서를 제어하는 방법 - async/await

promise를 더 간편하게 쓰는 방법으로 async/await 방법이 있다.
이는 비교적 최신 문법으로 then 대신에 async/await를 사용한다.

  • async 함수는
    • await 문이 없는 async 함수는 동기적으로 실행된다. 하지만 await 문이 있다면 async 함수는 항상 비동기적으로 완료된다.

🔗 async 참고: javascript MDN - async function

  • await 문은
    • async 함수 안에서만 사용 가능하다.
    • 항상 Promise 값을 반환한다. 만약 값이 Promise가 아니라면, 해당 값은 resolve된 Promise로 변환된다.
    • Promise가 이행(fulfill)되거나 거부(reject) 될 때까지 async 함수의 실행을 일시 정지한다.
    • Promise가 이행되면 async 함수를 일시 정지한 부분부터 실행하며,
      Promise에서 이행한 값을 반환한다.
    • Promise가 거부되면 Promise에서 거부한 값을 반환한다.

🔗 await 참고: javascript MDN - await

아래는 async/await의 예제이다.

//playNumber 함수는 임의의 숫자 num, (콜백)함수를 인자로 받아 숫자를 출력한다.
//숫자를 출력하는 시간은 매번 랜덤이다.
function playNumber(num, callback){
  //함수 정의시에는 일반 promise와 똑같이 작성한다.
  return new Promise((resolve, reject)=>{ 
    setTimeout(
      function(){
        console.log(num);
        resolve(); //함수를 실행한다.
  	  },
  	  Math.ceil(Math.random()*100)
    );
  });
}

//then이 아닌 async/await를 사용한다.
//화살표 함수는 이렇게 사용한다:
//const playAsyncAwait= async () => 
//async/await 실행
async function playAsyncAwait(){
  await playNumber(1);
  await playNumber(2); 
  await playNumber(3);
}

4) 타이머 메서드

자바스크립트에는 위 예제들의 setTimeout 처럼 일정시간 후에 실행되거나, 일정시간 간격으로 반복적으로 실행하는 메서드가 있다. 타이머 관련 API라고도 한다.

  • seTimeout(function, delay)
    일정 시간(delay) 후 콜백함수(function)가 실행된다.
    function은 타이머가 만료된 뒤 실행하며 delay는 함수가 실행되기 전의 일정시간으로 단위는 ms이다.(1초는 1000)
    비동기 내장함수이며, 메서드 자체는 타이머 ID를 리턴한다.

🔗 참고: javascript MDN - setTimeout()

  • clearTimeout(timerID)
    setTimeout()이 반환한 타이머 ID를 전달인자로 받아 실행하면, 그 타이머 ID를 반환한 setTimeout()의 동작을 멈춘다. 리턴값은 없다.

🔗 참고: javascript MDN - clearTimeout()

  • setInterval(function, delay)
    일정시간(delay)마다 반복해서 콜백함수(function)을 실행한다.
    메서드 자체는 interval ID를 리턴한다.

🔗 참고: javascript MDN - setInterval()

  • clearInterval()
    setInterval()이 반환한 interval ID를 전달인자로 받아 실행하면, 그 interval ID를 반환한 setInterval()의 동작을 멈춘다. 리턴값은 없다.

🔗 참고: javascript MDN - clearInterval()

Ch2. Node.js

Node.js는 비동기 이벤트를 기반으로 하는 JavaScript런타임(환경)이다. 비동기 API를 사용하거나 로컬 서버를 사용할 때 용이하다.

Node.js 모듈

모듈은 기능을 모아 하나로 묶은 코드를 의미한다. 이 모듈을 조립하여 사용할 수 있으며 Node.js에는 내장 모듈과 외부에서 만든 3rd party모듈이 있다.
내장 모듈은 공식 API문서로 확인 가능하다.
이 중에서 예를 들어 내장 모듈 중 하나인 fs(file system)모듈(파일을 읽거나 저장하도록 해준다)를 다음과 같이 찾아볼 수 있다.

Node.js 내장 모듈 사용하기

require구문을 사용하면 내장 모듈을 불러와서 사용할 수 있다.
(외장 모듈은 npm install <모듈명>로 설치 후 사용한다.)
아래 예제는 위의 공식문서에서 fs모듈을 찾아 사용해보는 예제이다.

const fs = require('fs'); //내장 모듈인 파일시스템 모듈 불러오기

const readData = (path, callback) => {
  fs.readFile(path, 'utf8', (err, data)=>{ //전달인자: 파일경로, 인코딩, 콜백함수 
    if(err) callback(err, null); //실패시 콜백함수에 에러를 전달하여 실행
    else callback(null, data); //성공시 콜백함수에 데이터를 전달하여 실행
  });
}

readData('myFolder/test.txt', (err, data)=>{
  console.log(data);
});

Ch3. fetch API

네트워크 요청은 대표적인 비동기 요청으로 주로 URL로 요청된다. fetch API를 사용하면 이 URL 요청이 가능하다.
fetch API를 통해 URL로부터 데이터를 받아오는 과정은 비동기로 이루어진다. 만약 fetch를 통해 URL을 불러오는 과정을 원하는 순서로 제어하고 싶다면 Ch1 비동기에서 언급된 콜백함수, Promise, async/await를 사용할 수 있다.

아래 예제는 url의 데이터들을 받아와 가공한 뒤 객체로 반환한다.

let url_01="https://myData.com/data01";
let url_02="https://myData.com/data02";

//방법1
function fetch01(){
  fetch(url_01)
    .then((response) => response.json())
  //json()은 네트워크 응답의 body부분을 받아온다.
    .then((urlData1)=>{
      return fetch(url_02)
      .then((response) => response.json())
      .then((urlData2)=>{
        return {urlData1, urlData2};
        //키 이름 없이 객체를 생성하면 변수명이 키 이름이 된다.
      })
      .catch(() => console.log('Error!') );
    })
    .catch(() => console.log('Error!') );
}

//방법2
function fetch02(){
  let urlData01=fetch(url_01)
  .then((response) => response.json());
  let urlData02=fetch(url_02)
  .then((response) => response.json());
  
  //Promise.all을 사용하면 각 Promise를 배열의 형태로 받아와서 활용할 수 있다.
  return Promise.all([urlData01, urlData02])
  .then(([urlData01, urlData02])=>{
    return {urlData1, urlData2};
  })
  .catch(() => console.log('Error!') );
}

//방법3
async function fetch03(){
  try{
    let urlData01=await fetch(url_01)
    .then((response) => response.json());
    let urlData02=await fetch(url_02)
    .then((response) => response.json());
  }catch{
    console.log('Error!');
  }

  return {urlData1, urlData2};
}

profile
프론트엔드 개발자 NH입니다. 시리즈로 보시면 더 쉽게 여러 글들을 볼 수 있습니다!

0개의 댓글