본문 바로가기

Ecmascript/Javascript

[비동기 처리] Promise와 async/await

비동기의 콜백방식 처리

사실 실무에서는 setTimeout처리보다는 ajax등 비동기통신으로 오는 데이터를 가지고 처리하는데서 발생하는 문제지만 간단한 설명/분석을 위해서 setTimeout으로 예를 들었다.

// 3초뒤에 2가 출력되는 것을 바라고 평범하게 코딩함
function doSomething() {
    let num=1;
    setTimeout(function(){
        num = 2;
    },3000);
    console.log(num); // 결과는 바로 1 출력
}

// callback 방식으로 처리
function callSomething(callback) {
    setTimeout(function(){
        callback(2);
    },3000);
}
callSomething(function(data){
    console.log(data) // 3초 뒤에 2 출력
})

지금은 간단하지만 중첩으로 계속 처리해야 될 경우 콜백의 뎁스가 깊어져서 헷갈린다.

Promise 사용

ES6의 Promise를 사용해서 콜백함수 방식 개선할 수 있다.

  1. Promise객체의 콜백함수 내에서 resolve함수를 실행시켜야 하고
  2. Promise객체에 then함수를 메서드체이닝 해서 실행하며
  3. then함수의 매개변수 === resolve함수에서 넘겨준 매개변수 라서 순차적 로직처리가 가능하다.
new Promise(function (resolve, reject) {
    setTimeout(function(){
        resolve("3초 뒤에 then으로 넘겨줄 데이터");
    }, 3000);
}).then(function(data){
    console.log(data) // "3초 뒤에 then으로 넘겨줄 데이터"
})

함수로 처리할때는 Promise객체를 return하게 해서 then으로 처리한다.

function promiseSomething() {
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            let num = 2;
            resolve(num);
        }, 3000);
    });
}

promiseSomething().then(function(data){
    console.log(data); // 3초 뒤에 2 출력
})

Promise객체의 콜백함수에 있던 resolve가 then이라는 메서드체이닝으로 실행되었다.

이정도 가지고는 콜백보다 딱히 뭐가 좋아진건지 아직 잘 와닿지 않는다.

reject()

Promise객체의 콜백함수에 있던 reject는 error handling을 처리해주는 함수

즉 try/catch의 catch문을 역할을 처리하는 함수이다.

function rejectSomething() {
    return new Promise(function (resolve, reject) {
        let num; // 일부로 reject쪽 조건 타도록 변수를 비움
          if(num !== undefined) { 
          setTimeout(function(){
              let num = 2;
              resolve(num);
          }, 3000);
        } else {
            reject(new Error("data가 선언되지 않았습니다."))
        }
    });
}

rejectSomething().then(function(data){
    console.log(data); // 조건절에서 resolve가 실행되지 못했기 때문에 여기를 타지 않음
}).catch(function(err){
    console.log(err); // "Error: data가 선언되지 않았습니다"
})

뎁스를 깊게 했을 때(2단계) 로직 실행 흐름과 에러 핸들링에 대해서 보자.

function chainingSomething() {
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
          let num = 2;  // 여기 num 값이 2라면 첫번째 then에서 Error가 발생   => err msg: data + 1는 1이 아닙니다
          resolve(num); // 위의 num 값이 0이라면 두번째 then에서 Error가 발생 => err msg: data + 1는 2가 아닙니다
        }, 3000);
    });
}

chainingSomething()
.then(function(data){
    console.log(data);
      if(data + 1 === 1) {
        return data + 1;
    } else {
        throw new Error("data + 1는 1이 아닙니다");
    }
})
.then(function(data){
    console.log(data);
    if(data + 1 === 3) {
        return data + 1;
    } else {
        throw new Error("data + 1는 2가 아닙니다");
    }
})
 .catch(function(err){
    console.log("err msg: ", err);
})

chainingSomething 함수의 resolve()에 담기는 num 값에 따라 첫번째 혹은 두번째 then에서 Error가 발생한다.

허나 catch 처리를 catch함수 하나에서 공통적으로 실행되기 때문에 매우 간결해진다.

실무에서는 catch에서 단순히 로그를 찍는다거나 아예 로직을 처음으로 롤백한다던지등 공통으로 처리하는 경우가 보통이기 때문에 매우 편한것 같다.

Promise의 장점

  1. then()으로 순차적으로 실행됬을때 처리를 하는것도 가능하고 메서드 체이닝 방식으로 주욱 나열해서 기술 가능하다.
  2. catch()로 then 내부에서 발생한 try/catch를 하나의 catch함수에서 간단하게 처리가 가능하다.
    # async/await

ES7의 sync/awiait로도 비동기 처리가 가능하다.

기존 Promise객체에서 제공하는 then함수로 바꿔서 처리하던 것을 기존 함수에 별칭 붙여서 처리한다고 생각하면 쉽다.

function promiseSomething() {
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            let num = 2;
            resolve(num);
        }, 3000);
    });
}

/*
기존 Promise.then() 방식
promiseSomething().then(function(data){
    console.log(data); // 3초 뒤에 2 출력
})
*/

async function action() {
    let data = await promiseSomething();
    console.log(data)
}
action();

Promise의 then()으로 처리했을 때보다 조금 길어져서 뭐가 좋으건지 잘 와닿지 않는다.

async/await는 비동기처리 하는 함수 여러개를 가지고 로직을 처리할 때 유용하다.

// 인증 완료 기능
function fnAuth() { 
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            let result = true;
            resolve(result);
              console.log("인증완료 되었습니다.");
        }, 1000);
    });
}

// 연산 기능
function fnCalc() { 
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            let result = 100;
            resolve(result);
        }, 1000);
    });
}

// 먼저 인증하고 인증이 완료된 경우에만 연산 처리를 해야되는 로직
async function action() {
    let auth = await fnAuth();
      if(auth) {
        let num = await fnCalc();
    }
      if(num === 100) {
        console.log("데이터가 정상 처리 되었습니다.");
    }

}
action(); // "인증완료 되었습니다." 1초 후에 "데이터가 정상 처리 되었습니다." 출력

async/await 에서 error handling

async함수 내부에 기존 함수 방식에서 쓰던 try/catch를 처리를 하면 된다.

// 위의 함수와 로직 동일
async function action() {
    try {
      let auth = await fnAuth();
      if(auth) {
          let num = await fnCalc();
      }
      if(num === 100) {
          console.log("데이터가 정상 처리 되었습니다.");
      }
    } catch(err) {
        console.log(err);
    }
}

Promise 와 async/await의 고찰

결국 ES6의 Promise와 ES7의 sync/awiait는 둘다 Promise를 객체를 사용하되
메서드체이닝 방식을 쓰느냐 기존 함수 방식을 쓰느냐의 차이다.

비동기함수 하나를 실행 후 실행결과를 물고물고 순차적으로 계속 처리할 때는 then()
비동기함수 여러개를 실행 결과로 복잡한 로직을 분기 처리할 때는 async/await

좀 더 가독성이 좋은것 같다는게 개인적인 느낌이었다.