[JS/Node] 비동기 (1)

이성은·2022년 11월 22일
0
post-custom-banner

1. 고차함수 리뷰

과제 - Underbar

2. 비동기

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

학습목표

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

2-1. 비동기 호출

  • callback 리뷰
    • 다른 함수(A)의 전달인자(argument)로 넘겨주는 함수(B)
    • 매개변수(parameter)를 넘겨 받은 함수(A)는 callback 함수(B)를 필요에 따라 즉시 실행(sync)할 수도 있고, 아니면 나중에 실행(async)할 수도 있다.
    • 함수 자체를 연결, 함수 실행을 연결하는 것이 아니다.
// <즉시 실행> 동기적으로 실행되고 있는 함수A의 콜백함수 B
function B() {
  console.log('콜백!!!');
}

function A(callback) {
  callback();
}

A(B) // '콜백!!!'
// 필요에 따라서 callback 함수를 비동기적으로 실행할 수도 있다.


// <상황 별 콜백>
//반복 실행하는 함수 (iterator) : forEach, map, filter 등 iterator 콜백이 반복 실행
[1,2,3].map((el, idx) => {
  return el * el;
}); //배열의 길이만큼 반복

//이벤트에 따른 함수 (event handler)
document.querySelector('#btn').addEventListener('click', (e) => {
  console.log('button clicked!');
});
  • 동기
    • 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것
    • 시작 시점과 완료 시점이 같은 상황을 "동기적(synchronous)이다." 라고 한다.
    • blocking : 하나의 작업이 끝날 때까지, 이어지는 작업을 막는 것
  • 비동기
    • 자바스크립트의 비동기 처리는 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것’을 의미
    • 자바스크립트는 싱글 스레드 기반으로 동작하는 언어, 따라서 동기적으로 작동 => 비동기 코드 작성

2-2. 비동기 JavaScript

  • JavaScript의 비동기 함수
    • JavaScript 자체 즉 JavaScript 엔진은 비동기 함수가 없음
    • JavaScript 런타임(환경: 브라우저, Node.js..)에서 제공하는 API를 이용
    • JavaScript는 싱글스레드 프로그래밍 언어이기 때문에 콜 스택이 하나 밖에 없음
    • 동기 함수를 제공하고, 비동기 함수가 콜 스택에 쌓이도록 도와주는 것 → 자바스크립트 런타임에서 제공
    • 아래 비동기의 주요 사례로 나온 API : 자바스크립트 런타임에서 제공되는 것
  • 비동기의 주요 사례
    • DOM Element의 이벤트 핸들러 : 마우스, 키보드 입력 (click, keydown 등), 페이지 로딩 (DOMContentLoaded 등)
    • 타이머 API (setTimeout 등), 애니메이션 API (requestAnimaitionFrame)..
    • 서버에 자원 요청 및 응답 : fetch API, AJAX (XHR)

< 타이머 관련 API >

  • 해당 API는 브라우저에서 제공하는 Web API이며 비동기로 작동하도록 구성
  • 밀리초 단위 시간(1000 = 1초)
  • setTimeout(callback, millisecond)
    • 일정 시간 후에 함수를 실행
    • 매개변수(parameter): 실행할 콜백 함수, 콜백 함수 실행 전 기다려야 할 시간 (밀리초)
    • return 값: 임의의 타이머 ID
setTimeout(function () {
  console.log('1초 후 실행');
}, 1000);
  • clearTimeout(timerId)
    • 매개변수(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);
  • clearInterval(timerId)
    • setInterval 타이머를 종료
    • 매개변수: 타이머 ID
    • return 값: 없음
const timer = setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
clearInterval(timer);

2-3. Callback

  • Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있다. 즉, 비동기를 동기화할 수 있다는 의미
const printString = (string, callback) => {
  setTimeout(function () {
    console.log(string);
    callback();
  }, Math.floor(Math.random() * 100) + 1);
};

const printAll = () => {
  printString('A', () => {
    printString('B', () => {
      printString('C', () => {});
    });
  });
};

printAll();

// A
// B
// C
  • Callback Hell
    • 콜백이 많아지면 코드관리 어려움, 가독성 떨어짐

2-4. Promise, Async/Await

  • 비동기로 작동하는 코드를 제어할 수 있는 다른 방법은 Promise를 활용하는 방법
  • Promise
    • new Promise
      • Promise는 class이기 때문에 new 키워드를 통해 Promise 객체를 생성
    • Promise는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달, 이 콜백 함수는 resolve, reject 함수를 인수로 전달받는다.
let promise = new Promise((resolve, reject) => {
	// 1. 정상적으로 처리되는 경우
	// resolve의 인자에 값을 전달할 수도 있습니다.
	resolve(value);

	// 2. 에러가 발생하는 경우
	// reject의 인자에 에러메세지를 전달할 수도 있습니다.
	reject(error);
});
  • Promise 객체의 내부 프로퍼티
    • new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다.
      => 직접 접근할 수 없고 .then, .catch, .finally 의 메서드를 사용해야 접근이 가능
    • State
      • 대기(pending) : 이행하지도 거부하지도 않은 초기 상태
      • 이행(fulfilled) : 연산이 성공적으로 완료됨
      • 거부(rejected): 연산이 실패함
    • Result
      • 처음은 undefined
      • 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동하여resolve(value)가 호출되면 value
      • 에러가 발생하여 reject(error)가 호출되면 error
  • Promise 인스턴스 메서드

1. Promise.then(onFulfilled, onRejected)

  • then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받는다.
    하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수
  • 반환 값 : Promise가 이행하거나 거부했을 때, 각각에 해당하는 핸들러 함수가 비동기적으로 실행
  • onFulfilled : Promise가 이행했을 때 호출되는 콜백 함수
  • onRejected : Promise가 거부될 때를 호출되는 콜백 함수 → catch와 같은 역할
  • 보통 거부한 Promise를 then의 2단 핸들러보다는 catch를 사용해 처리하는 경우가 많음
let promise = new Promise((resolve, reject) => {
	resolve("성공");
});

promise.then(value => {
	console.log(value);
	// "성공"
})

2.Promise.catch(onRejected)

  • executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근
  • 반환 값 : reject에 전달 인자로 넣어준 값
  • 호출되지 않을 경우 이행한 값을 그대로 사용해 이행하는 새로운 프로미스를 반환
let promise = new Promise(function(resolve, reject) {
	reject(new Error("에러"))
});

promise.catch(error => {
	console.log(error);
	// Error: 에러
})

3.Promise.finally()

  • executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근
  • 파라미터를 받지 않는다.
function checkMail() {
  return new Promise((resolve, reject) => {
    if (Math.random() > 0.5) {
      resolve('Mail has arrived');
    } else {
      reject(new Error('Failed to arrive'));
    }
  });
}

checkMail()
  .then((mail) => {
    console.log(mail);
  })
  .catch((err) => {
    console.error(err);
  })
  .finally(() => {
    console.log('Experiment completed');
  });
// Error: Failed to arrive
//"Experiment completed"
//또는
//"Mail has arrived"
//"Experiment completed"
  • Promise chaining
    • 비동기 작업을 순차적으로 진행해야 하는 경우
    • Promise chaining이 가능한 이유는 .then, .catch, .finally 의 메서드들은 Promise를 반환하기 때문
    • .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리
  • Promise.all()
    • 여러 개의 비동기 작업을 동시에 처리하고 싶을때 사용
    • Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료
const promiseOne = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () =>
  new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));


//Promise chaining
const result = [];
promiseOne()
  .then(value => {
    result.push(value);
    return promiseTwo();
  })
  .then(value => {
    result.push(value);
    return promiseThree();
  })
  .then(value => {
    result.push(value);
   console.log(result);  
	 // ['1초', '2초', '3초'] 총 6s걸림
  })

// promise.all
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
  .then((value) => console.log(value))
  // ['1초', '2초', '3초'] 총 3s걸림
  .catch((err) => console.log(err));

// promise.all 에러 발생
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이 발생
  • Promise + async/await
    • 복잡한 Promise 코드를 간결하게 작성
    • 비동기 작업을 수행하고자 하는 함수 앞에 async 키워드를 사용하고
    • async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await을 표기
    • 뒤의 내용을 Promise로 자동 전환하고, 해당 내용이 resolve된 이후에야 다음으로 진행
    • await 연산자는 Promise를 기다리기 위해 사용, resolve에 전달 인자로 넣어준 값 반환
// 함수 선언식
async function funcDeclarations() {
	await 작성하고자 하는 코드
	...
}

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

// 화살표 함수
const ArrowFunc = async () => {
	await 작성하고자 하는 코드
	...
}
    

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

const printAll = async () => {
  await printString('A');
  await printString('B');
  await printString('C');
};

printAll();

console.log(
  `Async/Await을 통해 Promise를 간결한 코드로 작성할 수 있게 되었습니다.`
);
// Async/Await을 통해 Promise를 간결한 코드로 작성할 수 있게 되었습니다.
// A
// B
// C
profile
함께 일하는 프론트엔드 개발자 이성은입니다🐥
post-custom-banner

0개의 댓글