[JavaScript/Node] 비동기(Asynchronous)

Seungmin Lee·2022년 7월 27일
0

JavaScript

목록 보기
13/14
post-thumbnail

비동기적(asynchronous) 처리를 위해 JavaScript에서는 콜백함수Promise, async/await를 사용한다.

비동기(Asynchronous)란?


JavaScript는 동기적(synchronous)이다. 즉, 호이스팅(hoisting)된 이후 부터 위에서 아래로 코드를 차례대로 동기적으로 실행된다는 의미이다. (호이스팅이란 코드 실행시 var변수와 함수 선언이 코드의 제일 위로 올라가는 것을 뜻한다.)

그렇다면 비동기(asynchronous)는 무엇일까? 어떠한 일이 끝날 때까지 아무것도 하지 않고 기다리는 것이 아닌 일이 끝나는 동안 또 다른 일을 병렬적으로 처리하는 것이다. 예를 들어 10초 뒤에 실행되는 A코드가 있다면, A코드가 실행될 때까지 10초동안 아무것도 하지않고 기다리는 것이 아닌, 10초 동안 A코드의 앞뒤로 다른 코드들이 실행된다. 그리고 10초가 지나면 A코드가 실행된다.

더 쉽게 말하자면, A가 커피를 주문하고 10초동안 A의 커피를 만들고 A의 커피가 나올 때 까지 B는 커피를 주문하지 못하고 기다려야 한다면, B는 A의 커피가 나오는 것과 동시에 B는 커피를 주문하게 될 것이다. 이렇게 A의 커피가 나오는 시점(완료 시점)과 B가 커피를 주문하는 시점(시작 시점)이 동일한 상황을 "동기적(synchronous)이다." 라고 한다. 반면에, B가 A의 커피가 만들어지는 중에 주문을 할 수 있는 상황이라면 "비동기적(Asynchronous)이다." 라고 한다.

비동기의 주요 예시

  • DOM Element의 이벤트핸들러(click, keydown 등)
  • 페이지 로딩 (DOMContentLoaded 등)
  • 타이머 API(setTimeout 등)
  • 애니메이션 API(requestAnimationFrame)
  • 서버에 자원 요청 및 응답(fetch API, AJAX(XHR))

Promise

Promise는 JavaScript에 내장되어 있는 비동기를 간편하게 처리할 수 있도록 도와주는 객체(Object)이다. 콜백함수도 비동기 처리를 위해 사용한다던데? 할 수도 있지만 콜백 체인이 길어질수록 콜백 지옥이 되기 때문에 Promise를 사용하여 깔끔하게 코드를 작성하는 것이 좋다.

new Promise

new키워드를 이용해 promise를 생성할 수 있고 promise에는 executor라는 콜백함수를 전달해줘야 한다.

이 때, executor 함수는 또 다른 두가지의 콜백함수를 전달 받는다.

  • 기능을 정상적으로 수행해서 최종 데이터를 전달하는 resolve 콜백함수
  • 기능을 수행하다 문제가 생기면 호출하게 될 reject 콜백함수

그리고 promise가 만들어짐과 동시에 excutor 함수가 실행되기 때문에 이 점을 꼭 유의해야 한다.

  • resolvereject 모두 호출하지 않으면 promisepending 상태가 되기 때문에 꼭 호출해줘야 한다. 호출 후 정상적으로 데이터가 전달되면 fulfilled 상태가 되고, 문제가 생기면 rejected 상태가 된다.
const promise = new Promise((resolve, reject) => {
  console.log('프로미스')
  setTimeout(() => {
    resolve('hello'); // 기능이 잘 수행되면 'hello'를 전달
  }, 2000);
});

then / catch

then

  • promise정상적으로 잘 수행이 된다면(then), 이 then은 잘 수행되었을 때의 값(value)을 전달받아 원하는 기능을 수행하는 콜백 함수를 전달받는다.
const promise = new Promise((resolve, reject) => {
  console.log('프로미스')
  setTimeout(() => {
    resolve('hello'); // 기능이 잘 수행되면 'hello'를 전달
  }, 2000);
  
promise.then( value => {
  console.log(value); // 2초 뒤에 'hello'가 출력됨
})
  • value파라미터에는 resolve에 전달된 값이 들어온다.
  • thenpromise를 리턴한다.

catch

  • promise가 정상적으로 수행이 되지 않고 에러가 발생 한다면, 이 catch는 에러(error)를 전달받아 에러 발생시 수행할 기능을 하는 콜백 함수를 전달받는다.
const promise = new Promise((resolve, reject) => {
  console.log('프로미스')
  setTimeout(() => {
    reject('에러 발생'); // 기능을 수행하다 문제가 생기면 '에러 발생'을 전달
  }, 2000);
  
promise
  .then( value => {
  	console.log(value);
  }) // then은 promise를 리턴하므로 바로 뒤에 체이닝으로 catch를 쓸 수 있다.
  .catch( error => {
  	console.log(error); // 2초 뒤에 '에러 발생'이 출력됨
  });
  • error파라미터에는 reject에 전달된 값이 들어온다.
  • thenpromise를 리턴하므로 Promise chaining으로 바로 뒤에 catch를 쓸 수 있다.

Promise chaining

아래의 코드처럼 체이닝으로 사용할 수 있다.

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1). 1000);
});

fetchNumber
.then(num => num *2) // 2
.then(num => num *3) // 6
.then(num => {
  return new Promise((resolve, reject) => {
    setTimeout( () => resolve(num -1), 1000); // 5
  });
})
.then(num => console.log(num)); // 5
// result : 콘솔에 5가 출력되고 총 2초가 소요된다.  

asyncawait

asyncawaitpromise를 사용하지 않고 비동기를 사용할 수 있도록 해주는 syntactic sugar(문법적 설탕)이다. asyncawait를 사용하면 마치 동기식 코드를 쓴 것 처럼 깔끔하게 쓸 수 있다.

async

  • 함수 앞에 async키워드를 쓰면 함수 내의 코드 블럭들이 자동으로 promise로 바뀐다.
  • async키워드가 붙은 함수는 promise를 리턴한다.
// promise를 이용해서 작성
function fetchUser(){
  return new Promise((resolve, reject) => {
    resolve('ellie');
  });
}

// async를 이용해서 작성
async function fetchUser(){
  return 'ellie';
}

const user = fetchUser();
user.then(console.log);
console.log(user);

await

  • 기다려!
  • async가 붙은 함수 내에서만 사용할 수 있다.
function delay(ms){
  // 정해진 ms가 지나면 resolve를 호출하는 Promise를 리턴
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 1초 후에 사과를 리턴하는 promise를 만드는 함수
async function getApple(){
  await delay(1000);
  throw 'error';
  return 🍎;
}

// 1초 후에 바나나를 리턴하는 promise를 만드는 함수
async function getBanana(){
  await delay(1000);
  return 🍌
}

async function pickFruits() {
  const apple = await getApple();
  const banana = await getBanana();
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log); // 2초 뒤에 🍎 + 🍌

Promise.all

  • 전달인자배열을 받는다. 이 때, 요소의 순서Promise.all의 Promise 순서와 상응한다.
  • 동시에 두 개 이상의 Promise 요청을 수행한다.
  • 두 개 이상의 Promise 요청이 전달될 때, 직전 Promise 요청이 거부된다면 모든 Promise 요청이 거부되면서 catch 메서드를 따라가게 된다.

fetch()

fetch함수는 promise 데이터 타입을 리턴한다. 또한 리턴값이 promise타입이므로 thencatch 메서드를 사용할 수 있고 이 메서드들은 콜백함수와 파라미터를 하나씩 갖는다. 이 때, then이 바로 promise이다.

아래의 코드를 통해 JSON의 데이터 타입을 JavaScript의 데이터 타입으로 바꿀수 있다. 또한 아래의 코드는 비동기적으로 처리된다.

fetch('https://jsonplaceholder.typicode.com/posts') //임의의 URL
.then(function(response){
  return response.json(); // response.json()의 실행 결과도 promise이므로 promise를 리턴하는 코드이다.
})
.catch(funciton(resason){
      console.log('reason', reason);
});
.then(function(data){ // 위의 then의 리턴값인 promise에 또 다시 then을 체이닝(chaining)해주면
  console.log('data', data) // JS타입의 데이터인 객체를 얻음

then 은 fetch를 통해 실행한 결과가 성공했을 때, then으로 전달된 콜백 함수가 호출된다. 호출된 콜백함수의 결과값이 있다면 첫번째 파라미터로 받는다.
catch는 fetch를 통해 실행한 결과가 실패했을 때, catch 안으로 전달된 콜백함수가 호출된다. 파라미터에는 실패한 이유가 전달된다.

Node.jsfs모듈

fs모듈은 로컬의 파일을 불러오거나 저장할 때 쓰는 모듈이다.

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

fs.readFile은 로컬에 있는 파일의 내용 전체를 비동기적으로 읽는 메서드이다. 이 메서드는 전달인자 세 개를 받는다.

path

path에는 파일명을 전달인자로 받는다. 네 가지 종류의 타입을 넘길 수 있지만 일반적으로 string타입을 받는다.

fs.readFile('/etc/passwd', ..., ...)

option

option은 넣을 수도, 넣지 않을 수도 있다. 문자열이나 객체 형태로 받을 수 있으며 문자열로 전달할 경우 인코딩을 받는다.

  • 하지만 써야하는 경우가 대부분이기 때문에 그냥 써주자!
// /etc/passwd 파일을 'utf8'을 사용하여 읽습니다.
fs.readFile('/etc/passwd', 'utf8', ...);

callback

callback에는 전달인자로 콜백 함수를 받고 파일을 읽고 난 후에 비동기적으로 실행된다. 또한 이 콜백 함수는 두 개의 파라미터 err data를 갖는다.

  • 에러가 발생하지 않으면 errnull이 된다.
  • data에는 문자열이나 Buffer라는 객체가 전달된다. data는 파일의 내용이다.
profile
<Profile name="seungmin" role="frontendDeveloper" />

0개의 댓글