[17] 비동기함수, async/await, Promise 체이닝, callback함수, setTimeout()

minjeong·2024년 1월 30일
0
post-thumbnail

1. 비동기함수

프로세스의 완료를 기다리지 않고 동시에 다른 작업을 처리하는 방식

1-1. setTimeout(code,delay)

  • delay 동안 기다리다가 code 함수를 실행

    특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행!!

      //setTimeout(code, delayTime); //delayTime 동안 기다렸다가 코드 실행
            console.log(1);
            setTimeout(() => {
                console.log(2);
            }, 2000);
         
            console.log(3);

➡️ 1,3,2 순으로 출력된다. 2는 2초동안 연산이 진행되고 있을 동안에 멈추지 않고 다음 코드(3)이 출력된 것이다.

📌 왜 비동기 처리일까?

  • 서버로 데이터를 요청 시, 서버가 언제 그 요청에 대한 응답을 줄지 모르는데 마냥 다른 코드를 실행 안하고 기다릴 수는 없다.!
  • 웹을 실행하는데 수십분이 걸릴 수도 있다..

예시코드

function goMart() {
                console.log('마트에서 어떤 음료수를 살까?! 고민된다');
            }
            function pickFood() {
                setTimeout(() => {
                    console.log('고민끝');
                    product = '생크림빵';
                    price = '3000';
                }, 3000);
            }
            function pay(product, price) {
                console.log(`상품명 : ${product}, 가격 : ${price}`);
            }
            let product;
            let price;
            goMart();
            pickFood();
            pay(product, price);

실행결과


-> (숫자는 생략해서 봐주길..!)
-> 상품명, 가격이 모두 undefined로 나오게 된다..!왤까?
-> 이유는 goMart() 다음에 pickFood()는 delay가 걸려버리므로 pay 함수가 그냥 바로 실행되어버린다.
-> 그래서 price, product가 undefined 되어버린다.

-> 이를 보완하기 위해 나온 개념이 콜백 함수이다.

2. callback 함수

  • Javascript 는 함수를 인자로 받고 다른 함수를 통해 반환될 수 있는데, 인자(매개변수)로 대입되는 함수를 콜백함수라고 한다.
  • 다른 함수가 실행을 끝낸 뒤 실행되는 함수
  • 함수를 선언할 때는 parameter(인자, 매개변수)로 함수를 받아서 쓸 수 있다.
  • 보통 함수를 선언한 뒤에 함수 타입 파라미터 를 맨 마지막에 하나 더 선언해주는 방식으로 정의

📌 왜 사용할까??!?
비동기 방식으로 작성된 함수를 동기 처리 하기 위해서다.
-> 독립적으로 수행되는 작업도 있는 반면, 응답을 받은 이후 처리되어야 하는 종속적인 작업도 있을 수 있으므로 그에 대한 대응방법이 필요하다 -> so, 콜백함수를 사용한다.

예시코드

//콜백함수 : 함수 타입 파라미터 맨 마지막에 하나 더 선언
            // 다른 함수가 실행을 끝낸 후 실행되는 함수
            function mainFunc(param1, param2, callback) {
                console.log(param1, param2);
                callback();
            }
            function callbackFunc() {
                console.log('콜백함수 입니다');
            }
            mainFunc(1, 2, callbackFunc);
           

앞선 잘못된 예시를 콜백함수를 이용해서 해결해보자.

예시코드

///1번 방법
function goMart() {
                console.log('마트에서 어떤 음료수를 살까?! 고민된다');
            }
            function pickFood() {
                setTimeout(() => {
                    console.log('고민끝');
                    product = '생크림빵';
                    price = '3000';
                    pay(product, price);
                }, 3000);
            }
            function pay(product, price) {
                console.log(`상품명 : ${product}, 가격 : ${price}`);
            }
            goMart();
            pickFood();

///2번 방법
function goMart() {
                console.log('마트에서 어떤 음료수를 살까?! 고민된다');
            }
            function pickFood(cb) {
                //cb = callback함수, 그냥 cb라는 매개변수
                setTimeout(() => {
                    console.log('고민끝');
                    product = '생크림빵';
                    price = '3000';
                    cb(product, price); 
                }, 3000);
            }
            function pay(product, price) {
                console.log(`상품명 : ${product}, 가격 : ${price}`);
            }

            goMart();
            pickFood(pay); //함수안에 pay함수를 불러오는 것  

2-1 콜백 지옥(callback Hell)

  • 비동기 프로그래밍 시 발생하는 문제이다.
  • 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어서 코드의 들여쓰기가 너무 깊어지는 현상
  • 가독성 감소, 코드 수정 난이도 상승
<script>
            //callback hell - 콜백함수가 2,3개정도는 들어갈 수 있지만, 그 이후는 너무 많고 복잡하다.
            setTimeout(function () {
                document.body.style.backgroundColor = 'red';
                setTimeout(function () {
                    document.body.style.backgroundColor = 'orange';
                    setTimeout(function () {
                        document.body.style.backgroundColor = 'yellow';
                        setTimeout(function () {
                            document.body.style.backgroundColor = 'green';
                            setTimeout(function () {
                                document.body.style.backgroundColor = 'blue';
                                setTimeout(function () {
                                    document.body.style.backgroundColor = 'navy';
                                    setTimeout(function () {
                                        document.body.style.backgroundColor = 'purple';
                                    }, 2000);
                                }, 2000);
                            }, 2000);
                        }, 2000);
                    }, 2000);
                }, 2000);
            }, 2000);
        </script>

3. Promise

  • callback Hell을 해결하기 위해 등장한 것이 Promise이다.

    비동기 함수를 동기 처리하기 위해 만들어진 객체

  • 성공과 실패를 분리해서 반환

  • 비동기 작업이 완료된 후에 다음 작업을 연결시켜 진행할 수 있는 기능을 가진다.

✏️ Promise 상태

3-1. 사용법

  • Promise는 두가지 콜백 함수를 가진다.
  • resolve(value) : 작업이 성공(fulfilled)한 경우, 그 결과를 value와 함께 호출 -> then메서드 실행
    reject(error) : 에러(rejected)발생 시 에러 객체를 나타내는 error와 함께 호출 -> catch 메서드 실행

예제 코드 (1)

//promise
            function promise1(flag) {
                return new Promise((resolve, reject) => {
                    if (flag) {
                        //  t/f를 나타낼때 쓰는게 flag
                        resolve('promise 상태는 fulfilled! then으로 연결됩니다. \n 이때의 flag는 true입니다.');
                    } else {
                        reject(`promise 상태는 rejected! catch로 연결됩니다.\n 이때의 flag는 false입니다.`);
                    }
                });
            }
            promise1(false) //true로 하면 resolve, false면 reject
                .then(function (result) {
                    console.log(result); //위의 메시지를 그대로 가져오고 싶기 때문에 함수에 매개변수를 만들어주고 받으면 됌.
                })
                .catch((error) => {
                    console.log(error);
                });

예제 코드 (2)

function goMart() {
                console.log('마트에 가서 어떤 음료를 살지 고민중');
            }

            function pickFood() {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('고민끝');
                        product = '코카콜라';
                        price = 2000;
                        resolve(); //성공할때 반환되어야하는게 setTimeout 함수니까.
                    }, 2000);
                });
            }
            function pay() {
                console.log(`상품명 : ${product}, 가격 : ${price}`);
            }
            goMart();
            pickFood().then(pay); //성공할때 then

3-2. Promise 체이닝

  • Promise 체이닝을 사용하지 않으면, 또다시 콜백지옥에 빠질 수 있다..!
    (1) 사용하지 않은 경우
//(4+3)*2-1 = 13
            //1. callback hell
            function add(n1, n2, cb) {
                setTimeout(() => {
                    let result = n1 + n2;
                    cb(result);
                }, 1000);
            }
            function mul(n, cb) {
                setTimeout(() => {
                    let result = n * 2;
                    cb(result);
                }, 700);
            }
            function sub(n, cb) {
                setTimeout(() => {
                    let result = n - 1;
                    cb(result);
                }, 500);
            }
            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); //이렇게 callback hell이 될 수 있음
                    });
                });
            });

(2) 사용한 경우

//promise
            function add1(n1, n2) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        let result = n1 + n2;
                        resolve(result);
                    }, 1000);
                });
            }
            function mul1(n) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        let result = n * 2;
                        resolve(result);
                    }, 700);
                });
            }
            function sub1(n) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        let result = n - 1;
                        resolve(result);
                    }, 500);
                });
            }
            add1(4, 3) //함수 안에서 사용할 매개변수
                .then((res) => {
                    console.log('add : ', res); // add : 7
                    return mul1(res);
                })
                .then((res1) => {
                    console.log('mul : ', res1); //mul : 14
                    return sub1(res1);
                })
                .then((res2) => {
                    console.log('sub : ', res2); //sub : 13
                });

Promise 체이닝 장점
1. then 메소드를 연속 사용하므로 순차적인 작업이 가능하다.
2. 예외처리가 간편하다. -> 마지막 catch 구문에서 한번에 에러 처리 가능!

//바로 위의 예시에 동일한 코드에 일부만 추가
function sub1(n) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        let result = n - 1;
                        //resolve(result);
                        reject(new Error('의도적 에러'));
                    }, 500);
                });
            }
            add1(4, 3)
                .then((res) => {
                    console.log('add : ', res);
                    return mul1(res);
                })
                .then((res1) => {
                    console.log('mul : ', res1);
                    return sub1(res1);
                })
                .then((res2) => {
                    console.log('sub : ', res2);
                })
                .catch((err) => {
                    console.log('실패');
                    console.log(err);
                });

예시코드

function call(name) {
                return new Promise((resolve) => {
                    setTimeout(function () {
                        console.log(name);
                        resolve(name);
                    }, 1000);
                });
            }
            function back(cb) {
                return new Promise((resolve, reject) => {
                    setTimeout(function () {
                        console.log('back');
                        resolve('back');
                    }, 1000);
                });
            }
            function hell(cb) {
                return new Promise((resolve, reject) => {
                    setTimeout(function () {
                        resolve('callback hell');
                    }, 1000);
                });
            }
            call('kim')
                .then((res) => {
                    console.log(res, '반가워');
                    return back();
                })
                .then((res1) => {
                    console.log(res1, '을 실행했구나');
                    return hell();
                })
                .then((res2) => {
                    console.log(`여기는 ${res2}`);
                });

4. async/await

  • Promise도 chaning을 하다보면 then() ...처럼 꼬리를 물게 되어 코드의 가독성이 저하될 수 있음
  • 직관적인 코드를 위해 만들어졌다.
  • 기능이 추가된 것이 아니라, Promise를 다르게 사용하는 것이다.

4-1. async

  • 함수 앞에 붙여 Promise를 반환한다.
  • Promise가 아닌 값을 반환해도 Promise로 감싸서 반환한다.
  • 비동기 함수 앞에서 사용

4-2. await

  • Promise 앞에 붙여 Promise가 다 처리될 때까지 기다리는 역할을 하며 결과는 그 후에 반환한다.
  • await은 async 함수 안에서만 사용 가능하다.

예시코드(1)

  • 바로 앞에서 작성했던 함수를 async/await로만 바꿔보자.
  • 앞의 코드들은 동일하니 생략하겠다.
 async function exec() {
                //async : await을 감싸고 있는 함수
                let user = await call('kim'); //await : promise 기반의 함수에다가 씀
                console.log(`${user} 반가워`);
                let result = await back();
                console.log(`${result}을 실행했구나`);
                let result1 = await hell();
                console.log(`여기는 ${result1}`);
            }
            exec();

📝 참고

  • 화살표 함수를 비동기함수로 사용했을 경우
   let exec = async () => {
              
            }

예시코드(2)

  • 앞에서 콜백지옥에서 보였던 배경색 변화하기 실습을 Promise를 이용해보았다.
function changeBgcolor(color, delayTime) {
                return new Promise((resolve) => {
                    setTimeout(() => {
                        resolve((document.body.style.backgroundColor = color));
                    }, delayTime);
                });
            }
            //delayTime도 각자 다르게 줄 수 있음
            async function exec() {
                await changeBgcolor('red', 1000);
                await changeBgcolor('orange', 3000);
                await changeBgcolor('yellow', 2000);
                await changeBgcolor('green', 2000);
                await changeBgcolor('blue', 1000);
                await changeBgcolor('navy', 1000);
                await changeBgcolor('purple', 1000);
            }
            exec();

-> 굉장히 빠르고 쉽게 가능하다!


마치며

점차 여러 함수를 쓰며 이전에는 몇십줄씩 작성해야 했던게, 지금은 코드길이가 준게 보인다. 콜백지옥 예제를 async/await로 바꿔보거나, Promise를 이용해서 바꿔봐야겠다.

profile
중요한 건 꺾여도 다시 일어서는 마음

0개의 댓글