본문 바로가기
IT/JavaScript

JS - 비동기

by 삐약 개발자 2024. 12. 18.

블로킹 이란?

  • 하나의 작업이 끝날때까지 이어지는 작업을 "막는것"

javascript 의 비동기적 실행이 효율적일때

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

동기란??

  • 동기 처리란 특정 코드의 실행이 완료 될때까지 기다리고 난 후에 다음 코드를 수행하는 것을 의미함.

비동기란?

  • 특정 코드의 실행이 완료 될때까지 기다리지 않고 다음 코드들을 수행하는 것을 의미함.
JAVASCRIPT 의 작동 원리

자바스크립트는 싱글 스레드 기반으로 동작하는 언어임.
따라서 동기적으로 작동하게 됨 하지만 작동하는 환경( 런타임 )에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할수 있음


비동기 JAVASCRIPT

타이머 관련 API

  • setTimeout(callback, millisecond) - 일정 시간 후에 함수를 실행
  • clearTimeout(timerId) - setTimeout 타이머를 종료
  • setInterval(callback, millisecond) - 일정 시간의 간격을 가지고 함수를 반복적으로 실행
  • clearInterval(timerId) - setInterval 타이머를 종료

비동기로 작동하는 코드를 제어할 수 있는 방법

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();
Promise
  • Promise는 class이기 때문에 new 키워드를 통해 Promise 객체를 생성
  • Promise는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve, reject 함수를 인수로 전달
  • Promise 객체가 생성되면 executor는 자동으로 실행되고 작성했던 코드들이 작동
  • 코드가 정상적으로 처리가 되었다면 resolve 함수를 호출하고 에러가 발생했을 경우에는 reject 함수를 호출

let promise = new Promise((resolve, reject) => { 
// 1. 정상적으로 처리되는 경우 // resolve의 인자에 값을 전달할 수도 있습니다. 
    resolve(value); 
// 2. 에러가 발생하는 경우 // reject의 인자에 에러메세지를 전달할 수도 있습니다. 
    reject(error); 
});
  • new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 가짐. 하지만 직접 접근할 수 없고 .then, .catch, .finally의 메서드를 사용해야 접근이 가능
State
  • 기본 상태는 pending (대기)
  • 비동기 처리를 수행할 콜백 함수가
    성공적으로 작동하여 fulfilled(이행)로 변경
    에러가 발생했다면 rejected(거부)로 변함
Result
  • 초기값 undefined
  • 비동기 처리를 수행할 콜백 함수가
    성공적으로 작동하여 resolve(value) 가 호출되면 value 로
    에러가 발생하여 reject(error) 가 호출되면 error 로 변함
then, catch, finally
Then

let promise = new Promise((resolve, reject) => {
    resolve("성공");
});

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

let promise = new Promise(function(resolve, reject) {
    resolve("성공");
    reject(new Error("에러"));
});

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

let promise = new Promise(function(resolve, reject) {
    resolve("성공");
    reject(new Error("에러"));
});

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

비동기 작업을 순차적으로 진행해야 하는 경우


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.all()은 여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용
인자로는 배열을 받음


Promise.all([promiseOne(), promiseTwo(), promiseThree()]) 
.then((value) => console.log(value)) // ['1초', '2초', '3초']
.catch((err) => console.log(err));
promise.chaining() , Promise.all() 예제

// 터미널에 'node index.js'을 입력하여 Promise.all()의 작동방식과 에러 발생시 동작방식을 이해해보세요!
// 또한 Promise chaining과의 차이점도 확인해보세요.

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));

// 1. 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);
   });

// 2. Promise.all()
 Promise.all([promiseOne(), promiseTwo(), promiseThree()])
   .then((value) => console.log(value))
   .catch((err) => console.log(err));

// 2-1. 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));

Promise 도 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell 발생함
Async/Await

JavaScript는 ES8에서 async/await키워드를 제공
복잡한 Promise 코드를 간결하게 작성할 수 있음
함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용
흐름을 제어하고 싶을때! async , 멈춰야 할때 await


// 함수 선언식
async function funcDeclarations() {
    await 작성하고자 하는 코드
    ...
}

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

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

사용하는 방법


// 터미널에 `node index.js`입력하여 비동기 코드가 작동하는 순서를 확인해보세요.
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를 간결한 코드로 작성할 수 있게 되었습니다.`
);