[JavaScript] 비동기처리(async/await)

2022. 7. 7. 21:38·Language/JavaScript
728x90

비동기 처리 방식

자바스크립트는 싱글 스레드 프로그래밍언어기 때문에 비동기처리가 필수적이다.

비동기 처리는 그 결과가 언제 반환될지 알수 없기 때문에 동기식으로 처리하는 기법들이 사용되어야 하는데, 대표적으로 setTimeout이 있고 callback과 promise가 있다.

세 가지 모두 비동기 코드를 동기식으로 작성하는데 휼륭한 기법들이지만, 모두 약간의 문제점을 가지고 있다.

async와 await는 이런 문제들을 해결함과 동시에 그 사용법에 있어서도 훨씬 단순해졌다.

// promise 문법
function p() {
	return new Promise((resolve, reject) => {
	    resolve('hello'); 
        // or reject(new Error('error');
	});
}
 
p().then((n) => console.log(n));
// async 문법
async function p2(){ // async을 지정해주면 Promise를 리턴하는 함수로 만들어준다.
  return 'hello2'; //리턴값은 Promise{<resolved>: "hello2"} 자동 resolve해준다는걸 알 수있다.
  // reject는 throw 에러를 날리면 된다.
}
 
p2().then((n) => console.log(n));

한눈에 봐도 매우 직관적인 코드로 변했다고 느낄수 있다.

함수에 async만 붙이면 자동 Promise객체로 인식되고 return값은 resolve()값과 똑같다.

return을 문자열로 했다고해서 일반 함수처럼 정말 문자열이 리턴되는 것은 아니다.

Promise를 리턴하게 해주는 지정자가 async이므로 리턴값은 Promise{<resolved>:"hello2"}

너무 헷갈리면 이렇게 직관적으로 Promise메소드를 줘도 된다.

async function p2(){ // async을 지정해주면 Promise를 리턴하는 함수로 만들어준다.
  //return 'hello2';
  return Promise.resolve('hello2'); // 똑같다
  // return Promise.reject('hello2');
}

async & await

async 와 await 는 절차적 언어에서 작성하는 코드와 같이 사용법도 간단하고 이해하기도 쉽다.
function 키워드 앞에 async만 붙여주면 되고

비동기로 처리되는 부분 앞에 await만 붙여주면 된다.

async가 붙은 함수는 프로미스를 반환하고, 프로미스가 아닌 것은 프로미스로 감싸 반환한다.

await 키워드를 만나면 프로미스가 처리(settled)될 때까지 기다린다.

그리고 프로미스가 처리가 완료되어 resolve(값) 되면 값만 따로 추출해서 리턴한다.

await는 promise.then보다 좀 더 세련되게 프로미스의 result 값을 얻을 수 있도록 해주는 문법이다.

promise.then보다 가독성 좋고 쓰기도 쉽다.

async/await가 Promise를 완벽히 대체하는 것이 아니다.
비동기는 Promise객체로 처리하고
async/await는 비동기를 동기식으로 처리하는 기법이다.
기존에는 Promise에 직접 .then()을 통해 동기처리를 했지만,
await를 사용하면 바로 동기처리가 되기 때문이다.
async function f() {
 
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });
 
  let result = await promise; // 원래라면, promise.then(()=>{})을 통해 비동기를 받아야하지만
                              // await 키워드로 직관적 이어졌다.
 
  alert(result); // "완료!"
}
 
f();
async function p2(){ // async을 지정해주면 Promise를 리턴하는 함수로 만들어준다.
  
  let result = await Promise.resolve("hello"); // 프로미스 객체의 then결과를 바로 받는다.
  return result;
}
 
p2(); // hello

async/await와 promise.then

async/await를 사용하면 await가 대기를 처리해주기 때문에 .then이 거의 필요하지 않다.

여기에 더하여 promise.catch 대신 일반 try..catch를 사용할 수 있다는 장점도 생긴다.

항상 그러한 것은 아니지만, promise.then을 사용하는 것보다 async/await를 사용하는 것이 대개는 더 편리하다.

그런데 문법 제약 때문에 async함수 바깥의 최상위 레벨 코드에선 await를 사용할 수 없다.

그렇기 때문에 관행처럼 .then/catch를 추가해 최종 결과나 처리되지 못한 에러를 다룬다.


astnc / await로 코드 최적화 예제

// 정통 Promise
function getApple(){
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve("apple"), 1000);
  })
}
 
function getBanana(){
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve("banana"), 1000);
  })
}
 
function getFruites(){
  getApple()
    .then((a) => { //리턴값이 곧 resolve()니까 then 가능
      getBanana()
        .then((b) => console.log(`${a} and ${b}`)); 
    }) // 콜백지옥 
}
 
getFruites() // 결과 : apple and banana

async 코드 최적화 #1

비동기 하는 함수 만들려면, 각 함수에다가 return new Promise()를 일일이 생성 해줘야 했다.

어차피 다 똑같은 프로미스인데 중복으로 작성되고 얼마나 비효율적인가?

프로미스 선언은 한번만 하고 각 함수에서 가져다 쓰는 방법은 없을까?

프로미스 비동기 함수를 하나 만들어주고, 동기 처리를 async로 하게 하면 된다.

delay()라는 프로미스 객체 비동기 함수를 만들고, 각 async함수 getApple,getBanana에서 동기처리를 한다.

function delay(){
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve(), 1000);
  })
}
 
async function getApple(){
  await delay();
  return "apple";
}
 
async function getBanana(){
  await delay();
  return "banana";
}
 
function getFruites(){
  getApple()
    .then((a) => { //리턴값이 곧 resolve()니까 then 가능
      getBanana()
        .then((b) => console.log(`${a} and ${b}`)); 
    }) // 콜백지옥
    
}
 
getFruites(); // 결과 : apple and banana

 

async 코드 최적화 #2

하지만 여전히 getFruites()에서 콜백지옥이 일어나 코드가 어지럽다.

생각해보니 굳이 async에서 리턴한 값(Promise.resolve(값))을 then으로 처리할 필요는 없다.

getApple()에서 리턴한 값(resolve(값))을 받으려면,

getApple().then((value) => {});

then()을 통해 아규먼트로 받을 수 있지만 저 value를 꺼내올 수 없어서 박스에서 처리해야한다는 단점이 있다.

반면에 await는 Promise가 끝날때까지 기다리면서, 끝나면 resolve(값)에서 값만 추출해 반환하는 특성을 가지고 있다.

let value = await getApple();

즉 resolve의 값을 추출해 변수에 넣으면 매우 간단하게 내부가 아닌 외부함수내에서 처리 가능하다.

function delay(){
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve(), 1000);
  })
}
 
async function getApple(){
  await delay();
  return "apple";
}
 
async function getBanana(){
  await delay();
  return "banana";
}
 
async function getFruites(){
  let a = await getApple(); //리턴값이 resolve()의 값이니까 대입 가능
  let b = await getBanana();
  console.log(`${a} and ${b}`);
}
 
getFruites(); // 결과 : apple and banana

 

async 코드 최적화 #3

하지만 문제점이 있다. 각 비동기를 순차적으로 기자려야 한다는 것이다.

let a = await getApple(); // 1초
let b = await getBanana(); // 1초
// 총 2초 기다려야함

apple과 banana는 서로 전혀 상관없는 관계이다.

1초만에 둘을 불러오고 처리하면 될건데, 지금 상태를 보면 총 2초가 걸리게 된다.

왜냐하면 하나씩 await를 했으니까

이를 병렬 처리해서 한번에 되도록 해보면(아래 예제)

async function getFruites(){
  let getApplePromise = getApple(); // async함수를 실행시킨다. 논블록킹으로 백단에서 실행되게 된다.
  let getBananaPromise = getBanana(); // 리턴으로 프로미스 객체로 감싸진 결과값을 받는다.
 
  let a = await getApplePromise; // 리턴으로 받은 프로미스객체를 done(data)를 await으로 빼서 변수에 넣음
  let b = await getBananaPromise; 
  // 본래라면 1초+1초 를 기다려야 하는데, 위에서 1초기다리는 함수를 한번에 불러왔기 때문에,
  // 대충 1.001초만 기다리면 동기식으로 처리된다.
  console.log(`${a} and ${b}`);
}

async / await의 예외 처리

(Promise의 reject()를 async에서 구현)

try..catch문을 쓰고 그냥 throw해주면 된다.

리턴값이 Promise객체이니 잡는 방법도 Promise.catch() 방법과 똑같다.

async function p2(){ 
  throw 'error';
  //throw new Error("error");
  //await Promise.reject(new Error("error"));
  //return Promise.reject(new Error("error"));
}
 
p2()
  .then((n) => console.log(n))
  .catch((n) => console.log(n));
function p(){
  return new Promise((resolve, reject) => {
    reject("Error!!");
  },1000)
}
 
(async function p2(){ 
  try{
    await p(); //Promise에서 reject가 될때까지 await한다.
  }catch(e){
    console.error(e);
  }
})();

setTimeout 과 async 함께 쓰기

예를 들어 1초 뒤에 비동기 함수 처리 하는 기법이다.

setTimeout(async () => {
   let res = await fetch("../data.txt");
   let text = await res.text();
}, 1000);

'Language > JavaScript' 카테고리의 다른 글

[JavaScript] JavaScript API  (0) 2023.01.21
[JavaScript] 바닐라 자바스크립트(Vanilla JavaScript)  (0) 2022.07.18
[JavaScript] RegExp 객체  (0) 2022.07.06
[JavaScript] 정규 표현식의 응용  (0) 2022.07.06
[JavaScript] 정규 표현식의 개념  (0) 2022.07.06
'Language/JavaScript' 카테고리의 다른 글
  • [JavaScript] JavaScript API
  • [JavaScript] 바닐라 자바스크립트(Vanilla JavaScript)
  • [JavaScript] RegExp 객체
  • [JavaScript] 정규 표현식의 응용
arajo
arajo
  • arajo
    아라 메모장
    arajo
  • 전체
    오늘
    어제
    • 분류 전체보기 (509)
      • Language (298)
        • HTML (55)
        • CSS (11)
        • JavaScript (70)
        • TypeScript (8)
        • Python (33)
        • Java (119)
        • C (0)
        • C# (2)
      • Programming (92)
        • Programming (14)
        • Web (51)
        • Apache (1)
        • MySQL (23)
        • AWS (3)
      • Framework | Library (26)
        • Framework | Library (3)
        • Vue.js (2)
        • React.js (5)
        • React Native (4)
        • Node.js (1)
        • Ajax (1)
        • Bootstrap (8)
        • Spring (1)
        • Flutter (1)
      • etc (2)
      • 휴식 (19)
        • 책 (13)
        • 일기 (5)
        • 게임 일기 (1)
      • A (71)
        • 공부 (18)
        • 기타 (6)
        • 일 (47)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    next.js
    react
    변수
    리액트
    타입스크립트
    event
    object
    제어문
    Java
    array
    web
    객체
    JavaScript
    TypeScript
    자바스크립트
    CSS
    HTML
    MySQL
    파이썬
    Python
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
arajo
[JavaScript] 비동기처리(async/await)
상단으로

티스토리툴바