ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [포스코x코딩온] 비동기 처리, 콜백, Promise, await
    [포스코x코딩온] 웹개발자 풀스택 부트캠프8기 2023. 7. 29. 14:59
    728x90

    비동기 처리

    • setTimeout(code, delay) 함수 이용 : delay 동안 기다리다가 code 함수 실행
    console.log(1);
    
    setTimeout(function () {
      console.log(2);
    }, 5000);
    
    console.log(3);
    
    setTimeout(function () {
      console.log(4);
    }, 2000);
    // 실행해보면 1-> 3 -> 4 -> 2로 나온다. 
    // 실행과정은 1이 출력되고 2가 출력되어하는데 5초 뒤에 실행되야 한다.
    // 그 사이에 3 이랑 4가 출력되는 것이다.

     

    • 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성
    • 비동기 처리를 하는 이유
      • 서버로 데이터를 요청 시, 서버가 언제 그 요청에 대한 응답을 줄 지 모르는데 마냥 다른 코드를 실행 안하고 기다릴 수 없기 때문이다.
      • 만약 에러가 나면 계속 응답을 기다려야 함으로 다른 코드를 먼저 실행시키기 위해서
    let product;
    let price;
    
    goMart();
    pickDrink();
    pay(product, price);
    
    function goMart() {
      console.log("마트 도착 음료를 골라보자");
    }
    
    function pickDrink() {
      setTimeout(function () {
        console.log("선택 완료");
        product = "콜라";
        price = "1000";
      }, 3000);
    }
    
    function pay(product, price) {
      console.log(`상품명: ${product}, 가격: ${price}`);
    }
    • 실행시키면 아래처럼 상품명과 가격에 undefined가 뜬다.

    • goMart()가 실행되고 pickDrink()가 실행되어야 하나 setTimeout() 함수로 인해 pay()가 먼저 실행된다. 그 상태에서 product와 price의 값이 할당되지 않았기에 undefined로 뜨게 된다. 이를 해결하기 위해 callback함수를 사용한다.

    콜백(callback) 함수

    • Javascript는 함수를 인자로 받고 다른 함수를 통해 반환할 수 있는데, 인자(매개변수)로 대입되는 함수를 콜백함수라 한다.
    • 다른 함수의 실행을 끝낸 뒤 실행이 되는 함수
    • 함수를 선언할 때 parameter(인자, 매개변수)로 함수를 받아서 쓸 수 있다.
    • 사용 이유
      • 비동기 방식으로 작성된 함수를 동기 처리하기 위하여
      • 독립적으로 수행되는 작업도 있는 반면 응답을 받은 이후 처리되어야 하는 종속적인 작업도 있을 수 있으므로 그에 대한 대응 방법이 필요하다.
    • 보통 함수를 선언한 뒤에 함수 파라미터를 맨 마지막에 하나 더 선언해주는 방식으로 정의
    //콜백함수 사용 예시
    //메인 함수, 함수 타입 파라미터 맨 마지막에 하나 더 선언
    
    function mainFunc(param1, param2, callback) {
      //콜백 함수에 결과 전달
      const result = param1 + param2;
      console.log("mainFunc");
      callback(result);
    }
    
    function callbackFunc(param) {
      console.log(param);
    }
    
    mainFunc(1, 2, callbackFunc);
    
    // mainFunc에서 param1에 1, param2에 2, callback에 callbackFunc가 할당되었다.
    // 그렇게 되면 result는 3이 나오고 callbackFunc의 param에 3이 전달된다.
    • 이를 사용하여 상품명과 가격에 할당하는 값을 띄울 수 있다.
    let product;
    let price;
    
    goMart();
    pickDrink(pay);
    
    function goMart() {
      console.log("마트 도착 음료를 골라보자");
    }
    
    function pickDrink(callback) {
      setTimeout(function () {
        console.log("선택 완료");
        product = "콜라";
        price = 1000;
        callback(product, price);
      }, 3000);
    }
    
    function pay(product, price) {
      console.log(`상품명: ${product}, 가격: ${price}`);
    }

    • 단점(콜백 지옥(Callback Hell))
      • 비동기 프로그래밍 시 발생하는 문제
      • 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기가 너무 깊어지는 현상
      • 가독성이 떨어지고 코드 수정 난이도가 상승한다.
      • 아래 코드처럼 계속 속성 변경을 계속하기 위해서 계속 들여써야 하는 문제점이 발생한다.
    //callback hell
    
    //1초마다 배경색 변경
    //빨 -> 주 -> 노 -> 초 -> 파
    
    setTimeout(function () {
      document.querySelector("body").style.backgroundColor = "red";
      setTimeout(function () {
        document.querySelector("body").style.backgroundColor = "orange";
        setTimeout(function () {
          document.querySelector("body").style.backgroundColor = "yellow";
          setTimeout(function () {
            document.querySelector("body").style.backgroundColor = "green";
            setTimeout(function () {
              document.querySelector("body").style.backgroundColor = "blue";
            }, 1000);
          }, 1000);
        }, 1000);
      }, 1000);
    }, 1000);
    • 이러한 단점을 해결하기 위해 Promise 사용

    Promise

    • 비동기 함수를 동기 처리하기 위해 만들어진 객체
    • 성공과 실패를 분리하여 반환
    • 비동기 작업이 완료된 이후에 다름 작업을 연결시켜 진행할 수 있는 기능을 가진다.
    • Promise의 상태
      • Pending(대기) : Promise를 수행 중인 상태
      • Fulfilled(이행) : Promise가 Resolve 된 상태(성공)
      • Rejected(거부) : Promise가 지켜지지 못한 상태. Reject된 상태(실패)
      • Settled : fulfilled 혹은 rejected로 결론이 난 상태

    • Promise는 두 가지 콜백 함수를 가짐
      • resolve(value) : 작업이 성공(fulfilled)한 경우, 결과로 value 호출 -> .then() 메서드 실행
      • reject(error) : 에러(rejected)발생 시 에러 객체를 나타내는 error와 함께 호출 -> .catch() 메서드 실행
    //promise 객체를 반환하는 promise1 함수 정의
    
    function promise1(flag) {
      return new Promise(function (resolve, reject) {
        if (flag) {
          resolve("promise 상태는 fulfilled, then으로 연결, 이때 flag값은 true");
        } else {
          reject("promise 상태는 rejected, catch로 연결, 이때 flag값은 false");
        }
      });
    }
    
     console.log(promise1(true));
    // 실행 결과 : Promise { 'promise 상태는 fulfilled, 이때 flag값은 true' }
    
    promise1(true)
      .then(function (result) {
        console.log(result);
      })
      .catch(function (err) {
        //에러 처리
        console.log(err);
      });
    
    //실행 결과 : promise 상태는 fulfilled, then으로 연결, 이때 flag값은 true
    
    promise1(false)
      .then(function (result) {
        console.log(result);
      })
      .catch(function (err) {
        //에러 처리
        console.log(err);
      });
    //실행 결과: promise 상태는 rejected, catch로 연결, 이때 flag값은 false
    • console.log(promise1(true))로 실헹하면 Promise{ 'promise 상태는 fulfilled, 이때 flag값은 true'}로 객체 형식으로 나옴
    • 아래 코드를 실행시켜 보면 if ~ else 문에서 resolve가 실행되면 then으로 연결되고, reject가 실행되면 catch로 연결되는 것을 확인할 수 있다. 
    let product;
    let price;
    
    goMart();
    pickDrink()
      .then(pay)
      .catch((err) => {
        console.log(err);
      });
    
    function goMart() {
      console.log("마트 도착 음료를 골라보자");
    }
    
    function pickDrink() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log("선택 완료");
          product = "콜라";
          price = 3000;
          if (price <= 2000) {
            resolve();
          } else {
            reject("돈이 부족해요.");
          } //작업 성공(resolve) 시 then으로 연결,  실패(reject) 시 catch로 연결
        }, 3000);
      });
    }
    
    function pay() {
      console.log(`상품명: ${product}, 가격: ${price}`);
    }
    • Promise 체이닝
      • then 메서드를 연속해서 사용이 가능하다. 
      • 순차적으로 작업이 가능하다.
      • 마지막에 catch 구문으로 예외처리를 한번에 할 수 있다.
    //Promise 체이닝
    
    //체이닝 사용 안 한 경우
    //(4+3)*2-1 연산
    
    function add(n1, n2, callback) {
      setTimeout(function () {
        let result = n1 + n2;
        callback(result);
      }, 1000);
    }
    
    function mul(n, callback) {
      setTimeout(function () {
        let result = n * 2;
        callback(result);
      }, 700);
    }
    
    function sub(n, callback) {
      setTimeout(function () {
        let result = n - 1;
        callback(result);
      }, 500);
    }
    
    //add -> mul -> sub
    add(4, 3, function (x) {
      console.log("1: ", x);
      mul(x, function (y) {
        console.log("2: ", y);
        sub(y, function (z) {
          console.log("3: ", z);
        });
      });
    });
    
    //#########################################
    //체이닝 사용 한 경우
    //장점 : then 메서드를 연속해서 사용 가능 -> 순차적으로 작업 가능
    //예외처리 간편 -> 마지막에 catch 구문으로 한 번에 처리 가능
    function add(n1, n2) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          let result = n1 + n2;
          resolve(result);
        }, 1000);
      });
    }
    
    function mul(n) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          let result = n * 2;
          resolve(result);
        }, 700);
      });
    }
    
    function sub(n) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          let result = n - 1;
          //resolve(result);
          reject(new Error("에러 처리"));
        }, 500);
      });
    }
    
    add(4, 3)
      .then(function (result) {
        console.log("1:", result);
        return mul(result);
      })
      .then(function (result) {
        console.log("2:", result);
        return sub(result);
      })
      .then(function (result) {
        console.log("3:", result);
      })
      .catch(function (err) {
        console.log(err);
      });

    async/await

    프로미스 기반 코드를 좀 더 쓰기 쉽고 읽기 쉽게 하기 위해 등장하였으며 비동기 처리 패턴 중 가장 최근에 나온 문법

    • async
      • 함수 앞에 붙여 Promise를 반환
      • 프로미스가 아닌 값을 반환해도 프로미스로 감싸서 반환
    • await
      • 프로미스 앞에 붙여 프로미스가 다 처리될 때까지 기다리는 역할을 하며 결과는 그 후에 반환
    // async / await
    
    //구조
    //함수 앞에 async 키워드 붙이기
    //비동기 처리 메서드 앞에 await 붙이기 (기다려줄 것)
    //async, await 항상 같이 쓰기
    
    let product;
    let price;
    exec();
    
    function goMart() {
      console.log("마트 도착 음료를 골라보자");
    }
    
    function pickDrink() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log("선택 완료");
          product = "콜라";
          price = 1000;
          if (price <= 2000) {
            resolve();
          } else {
            reject("돈이 부족해요.");
          }
        }, 3000);
      });
    }
    
    function pay() {
      console.log(`상품명: ${product}, 가격: ${price}`);
    }
    
    async function exec() {
      goMart(); //1번으로 실행
      await pickDrink(); //2번으로 실행
      pay(); //3번으로 실행
    }

     

    728x90
Designed by Tistory.