본문 바로가기

FrontEnd/JavaScript

[JS] 비동기 예제를 직접 만들면서 공부

자바스크립트 비동기 이해하기 ! 

비동기란 무엇인가 ??

 

비동기적 코드란 코드가 실행된 후 그 결괏값을 기다려주지 않고 곧바로 밑에 코드를 실행한다. 

 

사람이 비동기적 코드를 보면 결과를 예상하기 어려워 진다.

function example1() {
  console.log('안녕! 1');

  setTimeout(function () {
    console.log('안녕! 2');
  }, 1000);

  console.log('안녕! 3');
}

example1();

위 코드 결과를 보면 안녕! 1 , 안녕! 2,  안녕! 3 순서로 코드가 실행되는게 아니다. 

 

안녕 1 , 안녕 3 , 안녕 2 순으로 찍힌다. 

왜냐하면 코드를 쭉 실행하면서 두번째 코드를 실행시켜 놓고 코드는 곧 바로 밑에 코드를 실행 시키기 때문에 

결과가 다르게 나온 것 이다.

 

위 코드를 동기적으로 바꾸기 위해서는 콜백함수로 받아서 처리하는 방법이 있다.

function example2(callback) {
  console.log('안녕 1');

  setTimeout(function () {
    console.log('안녕 2');
    callback();
  }, 1000);
}

example2(() => {
  console.log('안녕 3');
});

하지만 콜백함수를 계속 연달아서 받다보면 콜백 헬 이라는 콜백현상 지옥에 빠지게 된다.

 

그래서 나온 개념이 프로미스라는 개념이다 ! 

 

Promise 

 

프로미스 쓰는 이유

   - 콜백 헬에 빠지지 않고 비동기 처리를 정상적으로 할 수 있다.
   - 최근에 많은 비동기 코드들이 프로미스 객체로 만들어 진다.

 

프로미스 만드는 방법 

  1. new 연산자를 통해서 만들 수 있다.
  2. 만든 프로미스는 컬백 함수를 인자로 받는데 그 안에는 2가지 파라미터가 들어간다 . (resolve , reject)

 

🌈 프로미스는 3가지 상태를 가진다.

    1. Pending (대기)
    2. Fullfilled(이행)
    3. Rejected (실패)

 

1. Pending (대기)

 new Promise( (resolve , reject) => {} )


-> resolve , reject 아무것도 반환하지 않은 상태 


  2. Fullfill (이행)

    new Promise( (resolve , reject) => {
      resolve()
    } )


    이행된 데이터는 then() 이라는 키워드를 통해서 데이터를 받을 수 있다.
   -> resolve() 가 실행된 상태

* 주의 : reject()를 catch()로 잡으면 개발자가 의도한 대로 오류를 잡은 거기 때문에 이러한 경우도 Fullfilled로 인식된다. 

 


  3. Rejected (실패)

    new Promise( (resolve , reject) => {
      reject()
    } )


    요청 실패된 데이터는 catch() 라는 키워드를 통해서 데이터를 받을 수 있다.

-> reject() 가 실행된 상태


  📌 Promise 개념정리
  1. 비동기 처리 방법 중 하나
  2. Promise는 new 연산자를 사용해서 만들어 낸다 .
  3. Promise는 컬백 함수 인자로 (resolve , reject) 를 받는데 , resolve는 then() , reject() catch() 데이터를 받는다.

 

Example 

1) 사용 

const promise = new Promise((resolve, reject) => {
  resolve('성공!');
});

promise
  .then((data) => {
    console.log(data); // 성공!
  })
  .catch((err) => {
    console.log(err);
  });

 

위 프로미스 객체는 resolve() 를 반환하기 때문에 그 resolve() 값은 then() 을 통해서 받을 수 있다.

 

2) 결괏값 then 으로 이어 받기 (Promise Chaining)

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 1000);
});

promise1
  .then((data) => {
    console.log(data);
    return data + 10;
  })
  .then((data) => {
    console.log(data);
    return data + 10;
  })
  .then((data) => {
    console.log(data);
    return data + 10;
  })
  .then((data) => {
    console.log('끝 !!', data);
  });

위 코드에서 resolve(10)  이라는 값은 1초 후에 실행이 됩니다.

그래서 promise1.then() 은 1초를 기다렸다가 그 resolve() 값이 오면 그때 실행이 됩니다. 

이때 then() 안에서 return 을 하면 그 return 값을 다시 then() 으로 받을 수 있습니다 !! 

 

위 코드는 10 , 20 , 30 , 끝 !! 40 이라는 순서로 코드가 console에 찍히게 됩니다 . 

 

3) resolve , reject 누가 먼저 ??

new Promise((resolve, reject) => {
  reject(0);
  resolve(1);
})
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err);
  });

reject() , resolve() 둘 다 존재하면 어떻게 결과가 나올까 ? 궁금해서 실험을 해본 코드인데 , 

reject()가 반환되면 더이상 밑에 resolve()는 실행이 되지 않습니다. 

 

결국 콘솔에는 0만 찍힙니다. 

 

4) 프로미스 값 예측해 보기

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('성공!!');
  }, 2000);
});
console.log(promise);
promise.then((data) => {
  console.log('1', data);
});
console.log('딴짓 ~ 1 ');
console.log('딴짓 ~ 2');
promise.then((data) => {
  console.log('2', data);
});

1) pending 

2) 딴짓 ~ 1

3) 딴짓 ~ 2

4)1 성공!!

5)2 성공!!

 

위 순서로 찍힌다. pending 이 찍힌 이유는 promise는 resolve() , reject() 를 반환하기 전까지는 무조건 pending 상태를 가진다.

그리고 딴짓 이 찍힌다음에 resolve() 가 반환되면서 4)  , 5) 이 실행이 된다.

 

async await

 

자바스크립트 비동기 처리 패턴중 제일 최근에 나온 따끈따끈 한 녀석
  1. 컬백함수와 프로미스 단점 보완

  사용 
  1. await 을 사용하려면 함수 앞에 async를 붙여준다 . 
  2. await 뒤에는 promise 객체가 온다. (꼭 프로미스 객체가 올 필요는 없지만 , 대체적으로 프로미스 처리를 위해서 사용됨)
  
  예외처리 (then , catch)
  async await 은 Promise랑 다르게 try {}, catch {}문법을 사용해서 예외처리를 한다.
  try , catch 후 무조건 실행해야 하는 코드가 있을 시 finally {}로 감싸준다.

🛠 Example 

1) async await 사용 예제

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('성공');
  }, 2000);
});

async function example() {
  let data = await promise;
  console.log(data);
}

example();

위 프로미스는 2초후 resolve('성공') 을 던지는 프로미스다 .

 

async 함수 안에서 await 은 then() 이랑 똑같은 역할을 한다.

resolve() 값을 받기 전까지 기다리다가 resolve() 값이 오면 그 후에 data 로 '성공' 이라는 값이 들어가게 된다. 

 

2)  프로미스 단점 보완 동기적 코드처럼 사용 

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 1000);
});

async function promiseChaing() {
  let data = await promise;
  console.log(data);
  data = data + 10;
  console.log(data);
  data = data + 10;
  console.log(data);
  data = data + 10;
  console.log('끝 !!', data);
}

promiseChaing();

위 코드는 아까 프로미스 예제 2) Promise Chaing 이랑 똑같은 결과를 보여준다.

하지만 다른 점은 이 예제는 async await 을 사용해서 코드를 보다 깔끔하고 사람이 읽기 쉽게 만들어 준다.

 

위 예제도 10초뒤에 반환되는 resolve(10) 이란 값을 await 으로 받고 그 후에 차근차근 10씩 더해가는 코드이다. 

 

3)  async await 예외처리

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('실패!!');
  }, 2000);
});

async function exception() {
  try {
    let data = await promise;
  } catch (error) {
    console.log(error);
  } finally {
    console.log('마지막!');
  }
}

exception();

async await 함수는 then() , catch() 랑 다르게 try {} , catch(err){} 로 예외처리를 한다.

위 코드 프로미스 reject('실패!!')는 2초후에 반환되는데 2초후 catch(error) {} 에서 reject() 값을 받아서 처리를 해준다!! 

 

finally() 같은 경우는 try , catch 둘 중 하나가 실행되고 나서 무조건 실행된다 ! 말 그대로 파이널리 !!

 

4)  async 함수 성질 이해하기

async function example() {
  return 20;
}

example().then((data) => {
  console.log(data);
});

(async () => {
  let data = await example();
  console.log(data);
})();

위 코드가 좀 재미있는 코드인데 , async 함수가 되면 return 값은 무조건 promise.resolve() 된 값으로 변환이 되서 

밑에 코드에서 then() , await 으로 20 이라는 return 값을 받을 수 있는 것 이다. 

 

결국 저 위에 return 20 함수는 아래와 같은 함수로 볼 수 있다.

async function example() {
  return new Promise((resolve, reject) => {
    resolve(20);
  });
}

 

비동기 부분은 사람이 이해하기 좀 더 어려울 수 있으므로 많은 코드를 보고 이해하는 것이 중요할 거 같다 !!