[TIL] Unit 3. 비동기

string_main·2022년 5월 29일
0

JavaScript

목록 보기
7/22
post-thumbnail

🌱 콜백 함수(Callback Function)


  • 정의 : 다른 함수의 전달 인자로 넘겨주는 함수.
  • 콜백 함수는 필요에 따라 즉시 실행(Synchronous)할 수도 있고, 나중에 실행(Asynchronous)할 수도 있다.
// 동기적인 실행 예시

function B() {
  console.log('hi');
}

function A(callback) {
  callback(); // callback === B
}

A(B);
  • 사용 예제

    • 반복 실행하는 함수 (iterator)
    [1, 2, 3].map(function(ele, idx) {
      return ele * ele;
    });
    • 이벤트에 따른 함수 (event handler)
    document.querySelector('#btn').addEventListener('click', function(e) {
      console.log('button clicked');
    });
    • 주의 사항 : 함수 실행을 연결하는 것이 아니라 함수 자체를 연결해야 함.
    function handleClick() {
      console.log('button clicked');
    };
    
    document.querySelector('#btn').onclick = handleClick(); // 이 경우에는 함수에 return값이 없기 때문에 undefined를 연결한 것과 같음. 괄호가 없어야 함수 자체를 연결하는 것이다.

🌱 동기(Synchronous) vs 비동기(Asynchronous)


  • 동기적(Synchronous) : 시작 시점과 완료 시점이 같은 상황

  • 비동기적(Asynchronous) : 시작 시점과 완료 시점이 같지 않은 상황.

  • 예시 1 :

    카페에서 커피를 주문하려고 줄을 서있는 상황!

    1. 먼저 온 사람이 주문한 커피를 받을 때 까지 다음 사람은 주문조차 할 수 없다고 한다.(blocking) ➡️ 동기적이다.

    2. 커피 주문을 언제든지 받을 수 있고 커피가 완성되는 즉시 커피를 진동벨로 알려 제공한다.(non-blocking) ➡️ 비동기적이다.

  • 예시 2 :

    전화문자
    하던 일을 멈추고 받아야 한다.(blocking)확인 후, 나중에 답장할 수 있다.(non-blocking)
    요청에 대한 결과가 동시에 일어난다.(Synchronous)요청에 대한 결과가 동시에 일어나지 않는다.(Asynchronous)

JavaScript is synchronous.

  • 자바스크립트는 기본적으로 한번에 한 줄씩 실행되므로 동기적인 처리 언어이다.

  • setTimeout, 이벤트 리스너, ajax 함수를 쓰면 비동기적인 처리도 가능하다.

  • JavaScript의 비동기적 실행(Asynchronous execution)이라는 개념은 웹 개발에서 유용하게 사용된다. 특히, 아래 작업은 비동기적으로 작동되어야 효율적이다.

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

Node.js를 만든 개발자도 위 대안이 합리적이라고 생각하여 Node.js는 non-blocking하고 비동기적(asynchronous)으로 작동하는 런타임으로 개발되었다.

  • 동기 예제
function waitSync(ms) {
  var start = Date.now();
  var now = start;
  while(now - start < ms) {
    now = Date.now();
  }
} // 현재 시각과 시작 시각을 비교하며 ms 범위 내에서 무한 루프를 도는 blocking 함수

function drink(person, coffee) {
  console.log(person + '는 ' + coffee + '를 마십니다');
}

function orderCoffeeSync(coffee) {
  console.log(coffee + '가 접수되었습니다');
  waitSync(2000); // 2초 동안 blocking
  return coffee;
}

let customers = [{
  name: 'Steve',
  request: '카페라떼'
}, {
  name: 'John',
  request: '아메리카노'
}];

// call synchronously
customers.forEach(function(customer) {
  let coffee = orderCoffeeSync(customer.request);
  drink(customer.name, coffee);
});

/*---------------------
카페라떼가 접수되었습니다
Steve는 카페라떼를 마십니다
아메리카노가 접수되었습니다
John는 아메리카노를 마십니다
-----------------------*/
  • 비동기 예제
function waitAsync(callback, ms) {
  setTimeout(callback, ms); // 특정 시간 이후에 callback 함수가 실행되게끔 하는 브라우저 내장 기능
}

function drink(person, coffee) {
  console.log(person + '는 ' + coffee + '를 마십니다');
}

function orderCoffeeSync(coffee) {
  console.log(coffee + '가 접수되었습니다');
  waitSync(2000);
  return coffee;
}

let customers = [{
  name: 'Steve',
  request: '카페라떼'
}, {
  name: 'John',
  request: '아메리카노'
}];

function orderCoffeeAsync(menu, callback) {
  console.log(menu + '가 접수되었습니다');
  waitAsync(function() {
    callback(menu);
  }, 2000);
}

// call asynchronously
customers.forEach(function(customer) {
  // 콜백 패턴
  orderCoffeeAsync(customer.request, function(coffee) {
    drink(customer.name, coffee);
  });
});

// +) 이벤트 등록 패턴
orderCoffeeAsync(request).onready = function(response) {
    drink(response);
  }

/*--------------------
카페라떼가 접수되었습니다
아메리카노가 접수되었습니다
Steve는 카페라떼를 마십니다
John는 아메리카노를 마십니다
-----------------------*/
  • 비동기의 주요 사례
    1. DOM Element 이벤트 핸들러 (click, keydown 등), 페이지 로딩(DOMContentLoaded)
    2. 타이머 API(setTimeout), 애니메이션 API(requestAnimationFrame)
    3. 서버에 자원 요청 및 응답 (fetch API, AJAX)

🌱 비동기 순서 제어


  • 비동기의 장점은 알겠지만...순서대로 실행되지 않는 문제가 있다.
const printString = (string) => {
  setTimeout(
    () => {
      console.log(string)
    },
    Math.floor(Math.random() * 100) + 1 // random 시간으로 기다리기 때문에 뭐가 먼저 끝날지 모름
  )
}

const printAll = () => {
  printString("A")
  printString("B")
  printString("C")
}
printAll() // 매번 순서가 다르게 나옴

🌿 Callback

const printString = (string, callback) => { // 콜백 함수를 받음
  setTimeout(
    () => {
      console.log(string)
      callback() // 콜백 함수 실행
    },
    Math.floor(Math.random() * 100) + 1
  )
}

const printAll = () => {
  printString("A", () => {
    printString("B", () => {
      printString("C", () => {})
    })
  })
}
printAll() // 순서대로 A, B, C 출력
  • Error Handling Design 사용 예시
somethingGonnaHappen = ((err, data) => {
  if(err) {
    console.log('ERR!!');
    return;
  }
  return data;
})

콜백..유용하지만? 연속해서 사용한다면 지옥이 펼쳐질 것이다..
This is Callback HELL..

🌿 Promise

  • ES6에서는 비동기 처리를 위한 패턴으로 프로미스(Promise)를 도입했다.

  • 콜백과 동일한 동작이지만, 코드 가독성이 향상된다.

  • .then()으로 다음 task를 동작할 수 있다.

  • 어느 곳에서 에러가 나더라도 .catch()로 에러를 잡을 수 있다. (콜백의 경우에는 콜백 처리 시 마다 에러 핸들링을 해줘야 함..)

  • 프로미스의 3가지 상태(states)

  1. pending(대기) : 비동기 처리가 수행되지 않은 Promise 객체가 생성만 된 상태
  2. fulfilled(이행) : resolve 콜백함수가 호출되어 비동기 처리가 실행 된 상태
  3. rejected(실패) : reject 콜백함수가 호출되어 비동기 처리가 실행 된 상태, 에러를 catch로 잡아주면 rejected 상태에서 fulfilled 상태로 바꿔줄 수 있다.
const printString = (string) => {
  return new Promise((resolve, reject) => { // Promise화 된 함수
    setTimeout(
    () => {
      console.log(string)
      callback() // 콜백 함수 실행
    },
    Math.floor(Math.random() * 100) + 1
    )
  })
}

const printAll = () => {
  printString("A").then(() => {
    return printString("B")
  })
  .then(() => {
    return printString("C")
  })
}
printAll() // 순서대로 A, B, C 출력

Promise도 HELL이 일어날 수 있기 때문에 return을 통해 다음 비동기로 넘기는 테크닉(Promise Chaining)이 필요하다.

  • 프로미스 체이닝(Promise Chaining)
// 코드의 일부만 가져온 예시, 눈으로 구조 익히는 용도!

// promise를 리턴하는 함수
const promiseConstructor = filePath => {
  return new Promise((resolve, reject) => { // resolve, reject는 콜백이자 exacutor 함수
    fs.readFile(filePath, 'utf-8', (err, data) => {
      if(err) reject(err); // 에러 발생 시 -> catch를 따라감
      else resolve(data); // 정상 수행 시 -> then을 따라감
    });
  })
};

// Promise Chaining 예시
const promiseChaining = () => {
  return promiseConstructor(pathParam1).then((user1) => {
    return promiseConstructor(pathParam2).then((user2) => {
      return [JSON.parse(user1), JSON.parse(user2)] // user1과 user2의 데이터를 JSON으로 파싱
    })
  })
  
promiseChaining();

🌿 async / await

  • Promise와 동일한 동작이지만, 일반 함수 처럼 동기적으로 작성할 수 있어 코드 가독성이 훨씬 향상되는 장점이 있다. (직관적임!)
  • async / await은 Promise의 문법적 설탕(Syntax Sugar)이다.
  • function 앞에 async를 붙이면 해당 함수는 항상 Promise를 반환한다.
function A() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('A') }, 300)
  })
}
function B() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('B') }, 200)
  })
}
function C() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve('C') }, 100)
  })
}

const printAll = async() => {
  const printA = await A()
  console.log(printA);
  
  const printB = await B()
  console.log(printB);
  
  const printC = await C()
  console.log(printC)
}

printAll()

실행 시간을 300, 200, 100 순으로 부여해도 await 키워드는 Promise가 처리될 때까지 기다리기 때문에 차례대로 실행된다.

🌱 타이머 API (JS 내장 비동기 함수)


  • setTimeout(callback, millisecond) : 일정 시간 후에 함수를 실행, 임의의 타이머 ID를 return
setTimeout(function () {
  console.log('1초 후 실행');
}, 1000);
  • clearTimeout(timerId) : setTimeout 타이머를 종료, return 값 없음
const timer = setTimeout(function () {
  console.log('10초 후 실행');
}, 10000);

clearTimeout(timer); // setTimeout이 종료됨.
  • setInterval(callback, millisecond) : 일정 시간의 간격을 가지고 함수를 반복적으로 실행, 임의의 타이머 ID를 return
setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
  • clearInterval(timerId) : setInterval 타이머를 종료
const timer = setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
clearInterval(timer);
// setInterval이 종료됨.

🌱 Node.js


Node js is an asynchronous event-driven JavaScript runtime.

  • 정의 : Node.js는 비동기 이벤트 기반 JavaScript 런타임이며, 확장 가능한 네트워크 애플리케이션을 빌드하도록 설계되었다.

  • runtime : 런타임은 프로그램이 실행되고 있는 때 존재하는 곳, 즉, 프로그래밍 언어가 구동되는 환경이다.

🌿 Node.js 내장 모듈

  • 모듈(module) : 어떤 기능을 조립할 수 있는 형태로 만든 부분. 예를 들어, fs(File System) 모듈은 PC의 파일을 읽거나 저장하는 등의 일을 할 수 있게 도와준다.
    ex)
    • readFile() : 파일을 읽을 때 사용하는 메서드
    • writeFile() : 파일을 저장할 때 사용하는 메서드

브라우저에서 다른 파일을 불러올 때 <script>를 사용했듯이, Node.js에서는 코드 가장 상단에 require를 사용하여 다른 모듈을 불러온다!

const fs = require('fs'); // 파일 시스템 모듈을 불러옴
const dns = require('dns'); // DNS 모듈을 불러옴

// 이후 fs.readFile 메서드 등을 사용할 수 있음

🌿 3rd-party 모듈

  • 정의 : 공식적으로 제공하는 빌트인 모듈(built-in module)이 아닌 외부 모듈 (ex. underscore)
// 서드 파티 모듈을 다운로드 하기 위해서 npm을 사용한다.
npm install underscore
const _ = require('underscore'); // underscore 모듈을 불러옴

🌿 공식 문서 가이드

내장 모듈 공식 문서
공식 문서를 애용하자!! (번역기 바로 돌리지말고 영어로 일단 이해하려고 노력하기)

fs.readFile은 비동기적으로 파일 내용 전체를 읽는 모듈이다.

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

  • path <string> | <Buffer> | <URL> | <integer>
    path에는 파일 이름을 전달인자로 받습니다. 네 가지 종류의 타입을 넘길 수 있지만 일반적으로 문자열의 타입을 받습니다.

  • option <Object> | <string>
    대괄호로 감싼 두 번째 전달인자 options는 넣을 수도 있고, 넣지 않을 수도 있습니다. 대괄호는 선택적 전달인자를 의미합니다.
    options는 문자열 또는 객체 형태로 받을 수 있습니다. 문자열로 전달할 경우 인코딩을 받습니다. 밑의 예제에서는 'utf8' 을 두 번째 전달인자로 받는 것을 확인할 수 있습니다.

  • callback <Function>

    • err <Error> | <AggregateError>
    • data <string> | <Buffer>

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

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

    인코딩이 지정되지 않으면 원시 버퍼가 반환됩니다.

fs.readFile('test.txt', 'utf8', (err, data) => {
  if (err) {
    throw err; // 에러를 던던던던 던져!
  }
  console.log(data);
});

🌱 fetch API


  • 비동기 요청의 가장 대표적인 사례는 단연 네트워크 요청!
  • fetch API는 특정 URL로 부터 정보(data)를 받아오는 역할을 한다. 이 과정은 비동기적으로 이루어진다.
  • URL로 요청하는 것을 가능하게 해주는 API이다.
  • 포털 사이트에는 실시간으로 변하는 정보(날씨 정보, 뉴스 등)와 고정적인 정보가 분리되어 있으며, 해당 정보만 업데이트하기 위해 요청 API를 이용한다.

원격 URL로부터 정보를 받아옴 -> 특정 DOM 엘리먼트를 업데이트

let url = "https://v1.nocodeapi.com/codestates/google_sheets/YbFMAAgOPgIwEXUU?tabId=최신뉴스";
fetch(url)
  .then((response) => response.json())
  .then((json) => console.log(json))
  .catch((error) => console.log(error));
  • Node.js 환경에는 fetch API가 내장 모듈로 제공되지 않아서 브라우저에서 테스트를 진행해야 한다.

🌱 Check List


스스로에게 질문하는 시간!! 말로 설명해보자.

  • 동기와 비동기 개념에 대해 설명할 수 있는가?
  • callback, Promise, async/await의 장단점을 이해하고 작동 원리를 이해할 수 있는가?
  • Promise 실행 함수가 가지고 있는 resolve와 reject는 각각 무엇을 의미하는가?
  • new Promise()로 생성한 인스턴스에는 어떤 메서드가 존재하고 각각은 어떤 용도인가?
  • Promise.prototype.then 메서드는 무엇을 리턴하는가?
  • Promise.prototype.catch 메서드는 무엇을 리턴하는가?
  • Node.js 공식 문서를 활용해 내장 모듈을 활용할 수 있는가?
  • fetch에 대해 이해하고 데이터를 불러올 수 있는가?

🌱 추가 학습


  • Event Loop
  • Callback Queue
  • XHR

| 참고자료 |

profile
FE developer

0개의 댓글