S2_U3_CH2. 비동기

Judevv·2023년 5월 16일
0

Chapter 2. 비동기

학습 목표

  • 어떤 경우에 중첩된 콜백(callback)이 발생하는지 이해할 수 있다.
  • 중첩된 콜백(callback)의 단점, Promise의 장점을 이해할 수 있다.
  • async/await 키워드에 대해 이해하고, 작동 원리를 이해할 수 있다.

2-1. 동기와 비동기


  • 동기(synchronous)

    • 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것
    • 예를 들면, 작업 A가 완료될 때까지 작업 B는 기다려야하는데, 작업 A가 시간이 오래 걸린다면, 작업 B는 그동안 아무 일도 하지 못하고 대기해야 함
  • 비동기(asynchronous)

    • 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것
    • 작업 A를 시작한 후에 작업 B를 시작할 수 있고, 작업 A가 완료될 때까지 작업 B는 기다리지 않고 다른 작업을 수행할 수 있음(작업 A가 완료되면, 작업 B는 다시 이어서 실행)

💡) 동기, 비동기 쉽게 이해하기
친구에게 메시지를 보내고 응답을 기다리는 상황을 생각해보자.
동기적으로 처리한다면, 메시지를 보낸 후에 친구가 응답할 때까지 기다려야한다.
하지만 비동기적으로 처리한다면, 메시지를 보낸 후에 다른 일을 할 수 있고, 친구가 응답하면 그 때 처리할 수 있다.

  • JavaScript의 작동원리
    • JavaScript는 싱글 스레드 기반으로 동작하는 언어 → 동기적으로 작동
    • 런타임에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리 가능

💡) JavaScript에서의 비동기 이해하기
웹 페이지에서 이미지를 로드하는 경우
동기적인 방식으로 이미지를 로드한다면, 이미지를 다운로드할 때까지 다른 작업은 중단되어 사용자는 그동안 아무것도 할 수 없게 된다.
하지만 비동기적인 방식으로 이미지를 로드한다면, 이미지를 다운로드 하는 동안에도 다른 작업을 처리할 수 있고, 사용자는 웹 페이지를 계속 탐색하거나 다른 동작을 할 수 있다.

* 스레드(thread)
  - 프로그램 내에서 실행되는 독립적인 작업의 흐름
* 멀티 스레드
  - 스레드를 동시에 실행하여 여러 작업을 동시에 처리
  - 하나의 스레드가 사용자 인터페이스를 업데이트하고, 다른 스레드가 파일을 읽어오는 작업 처리와 같은 걸 할 수 있음
* 싱글 스레드
  - 한 번에 하나의 작업만을 처리할 수 있음
  

2-2. 비동기 JavaScript


  • 타이머에 관련된 API로 비동기를 경험해보기

💡) API가 뭘까?
API(Application Programming Interface)는 프로그램과 다른 프로그램 또는 서비스 간에 상호작용을 하기 위한 규칙과 도구의 집합이다.
프로그램이 다른 프로그램과 정보를 주고 받을 수 있도록 하는 역할을 한다.
예를 들어, 날씨 정보를 알고 싶다면, 날씨 서비스의 API를 사용해서 프로그램이 날씨 정보를 얻을 수 있게 한다.
프로그램들이 서로 소통하고 상호작용하는 방법을 정의하는 일종의 계약이라고 할 수 있고, 어떤 종류의 데이터를 요청할 수 있는지, 어떻게 요청해야 하는지, 어떤 종류의 응답을 받을 수 있는지에 대한 규칙을 제공한다.

  • 타이머 관련 API
    • setTimeout(callback, millisecond)
      • 일정 시간 후에 함수 실행
      • 매개변수(parameter): 실행할 콜백 함수, 콜백 함수 실행 전 기다려야 할 시간(밀리초)
      • return 값: 임의의 타이머 ID
      setTimeout(function () {
       console.log('1초 후 실행');
      }, 1000);
      // 123
    • clearTimeout(timerId)
      • setTimeout 타이머를 종료
      • 매개변수(parameter): 타이머 ID
      • return 값: 없음
      const timer = setTimeout(function () {
      console.log('10초 후 실행');
      }, 10000);
      clearTimeout(timer);
      // setTimeout이 종료됨.
    • setInterval(callback, millisecond)
      • 일정 시간의 간격을 가지고 함수를 반복적으로 실행
      • 매개변수(parameter): 실행할 콜백 함수, 반복적으로 함수를 실행시키기 위한 시간 간격 (밀리초)
      • return 값: 임의의 타이머 ID
      setInterval(function () {
      			  console.log('1초마다 실행');
      			}, 1000);
      			// 345
      			```
    • clearInterval(timerId)
      • setInterval 타이머를 종료
      • 매개변수: 타이머 ID
      • return 값: 없음
      const timer = setInterval(function () {
      			  console.log('1초마다 실행');
      			}, 1000);
      			clearInterval(timer);
      			// setInterval이 종료됨.
  • 비동기 코드
    • 작동된 순서대로 작동되는 것이 아닌, 동작이 완료되는 순서대로 작동
    • 코드의 순서를 예측할 수 없음!
  • 프로그래밍을 하면서 개발자가 제어할 수 없는 코드를 작성하는 것은 좋지 않음 → 비동기로 작동하는 코드를 제어할 수 있는 방법을 알아야 함

2-3. Callback


  • 콜백함수(callback)

    • 다른 함수에 인자로 전달되어 특정 작업이 완료되었을 때, 실행되는 함수
    • 비동기 작업이 완료되면 콜백함수가 호출되어 추가적인 처리 가능
  • 일반적으로 콜백함수는 비동기 함수의 마지막 인자로 전달되며, 비동기 작업이 완료되면 콜백 함수가 호출됨 → 비동기 작업의 결과를 받아서 처리하거나 추가 동작 수행

  • 예시

function delayedHello(callback) {
  setTimeout(function() {
    console.log("Hello, World!");
    callback();
  }, 1000);
}

// delayedHello 함수는 콜백 함수를 인자로 받아서, 1초 후에 "Hello, World!"를 출력한 뒤에 콜백 함수를 호출

function afterDelay() {
  console.log("After delay");
}

// afterDelay 함수는 콜백 함수로 전달되어서 "After delay"를 출력

delayedHello(afterDelay);
  • 콜백 함수를 중첩하거나 관리가 어려워지는 경우(callback hell), Promise나 async/await와 같은 다른 비동기 제어 메커니즘을 사용할 수 있음

2-4. Promise, Async/Await


Promise

  • new Promise

    • Promise는 class이기 때문에 new 키워드를 통해 promise 객체 생성
    • 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달 받음
      • resolve, reject 함수를 인수로 전달 받음
  • Promise 객체가 생성되면 executor는 자동으로 실행되고 작성한 코드들이 작동

    • 정상적으로 처리가 되었다면 resolve 함수 호출
    • 에러가 발생했을 경우 reject 함수 호출
  • Promise 객체는 state, result 내부 프로퍼티를 가짐

    • 직접 접근은 안되고, .then, .catch, .finally 메서드를 사용해서 접근
  • State

    • 기본 상태는 pending(대기)
    • 성공적으로 작동했다면 fulfilled(이행)으로 변경
    • 에러가 발생했다면 rejected(거부)
  • Result

    • 처음은 undefined
    • 콜백 함수가 성공적으로 작동하여 resolve(value)가 호출되면 value로 변경
    • 에러가 발생하여 reject(error) 호출되면 error로 변경

then, catch, finally

  • Then

    • executor에 작성한 코드가 정상적으로 처리가 됐으면, resolve 함수를 호출하고 .then 메서드로 접근할 수 있음
    • .then 안에서 리턴한 값이 Promise면 내부 프로퍼티의 result를 다음 .then의 콜백함수의 인자로 받아옴
  • Catch

    • executor에 작성한 코드들이 에러가 발생했을 경우 reject 함수를 호출하여 .catch 메서드로 접근 가능
  • Finally

    • executor에 작성한 코드들의 정상처리 여부와 상관 없이 .finally 메서드로 접근 가능

  • 예시

// "요청 시작"을 출력한 후 2초를 기다린 뒤 데이터를 받아온 메시지를 출력하고 "요청 완료"를 출력, 만약 데이터를 받아오지 못했다면 오류 메시지가 출력하는 예시

function fetchData() {
  return new Promise(function(resolve, reject) {
    // 비동기 작업 시뮬레이션
    setTimeout(function() {
      const data = "데이터가 도착했습니다!";
      // 데이터를 성공적으로 받아온 경우
      if (data) {
        resolve(data);
      } else {
        reject("데이터를 받아오지 못했습니다.");
      }
    }, 2000);
  });
}

// fetchData 함수를 호출하여 데이터를 가져오는 Promise를 생성하고
// fetchData 함수는 2초 후에 데이터를 성공적으로 받아온 경우 resolve를 호출하고, 그렇지 않은 경우 reject를 호출

console.log("요청 시작");

fetchData()
   // then을 사용하여 Promise가 성공적으로 해결된 경우 데이터를 출력
  .then(function(response) {
    console.log("데이터 받음:", response);
  })
  
   // catch를 사용하여 Promise가 실패한 경우 오류 메시지를 출력
  .catch(function(error) {
    console.log("오류 발생:", error);
  })
  
   // finally는 Promise가 해결되었든 실패했든 상관없이 항상 실행
  .finally(function() {
    console.log("요청 완료");
  });

Promise chaining

  • 비동기 작업을 순차적으로 진행해야 하는 경우에 필요
  • .then, .catch, .finally 의 메서드들은 Promise를 리턴하기 때문에 Promise chaining이 가능
  • 따라서, .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리함
let promise = new Promise(function(resolve, reject) {
	resolve('성공');
	...
});

promise
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .catch((error) => {
    console.log(error);
    return '실패';
  })
  .finally(() => {
    console.log('성공이든 실패든 작동!');
  });

Promise.all()

  • 여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용
  • 인자로는 배열!을 받음
  • 해당 배열에 있는 모든 Promise에서 executor 내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise를 반환
  • Promise.all()은 인자로 받는 배열에 있는 Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료
  • 아래의 예시와 같이 1초 후에 에러가 발생하고 Error: 에러1이 반환된 후로는 더 이상 작동하지 않고 종료됨
 Promise.all([
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러1'))), 1000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러2'))), 2000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러3'))), 3000),
])
  .then((value) => console.log(value))
  .catch((err) => console.log(err));
	// Error: 에러1

Promise Hell

  • Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있음

Async/Await

  • JavaScript는 ES8에서 async/await키워드를 제공
  • 함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용
// 함수 선언식
async function funcDeclarations() {
	await 작성하고자 하는 코드
	...
}

// 함수 표현식
const funcExpression = async function () {
	await 작성하고자 하는 코드
	...
}

// 화살표 함수
const ArrowFunc = async () => {
	await 작성하고자 하는 코드
profile
감성있는 개발자를 꿈꿔요

0개의 댓글