본문 바로가기
JavaScript/JavaScript

Javascript) Promise (기본, setTimeout, Promise chain)

by 박채니 2022. 6. 3.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


Promise

- 기존 callback 함수를 통한 비동기처리를 개선하기 위한 문법

- producer/consumer 코드를 연결

  * producer (비동기처리가 포함된 코드)

  * consumer (비동기 완료 후 실행할 코드)

 

속성

- status

- pending → fulfilled (이행) / rejected (거부)

 

- result

- undefined → value (이행 시) / error 객체 (거부 시)

 


Promise 기본

 

HTML 코드

<button id="btn1">basics</button>

 

Javascript 코드

 

- resolve : 이행(정상처리) 시 호출할 콜백함수 → then에서 전달

- reject : 거부 시 호출할 콜백함수 → catch에서 전달

btn1.onclick = () => {
    const promise = new Promise((resolve, reject) => {
        console.log("producing...");
    });
    console.log(promise);
};

state와 result가 'pending', 'undefined'인 것을 확인할 수 있습니다.

resolve가 실행 되면 fulfilled로 변경되며, reject가 실행되면 rejected 상태로 변경될 것입니다.

 

홀수가 나오면 resolve, 짝수가 나오면 reject로 상태 변화를 확인해보겠습니다.

btn1.onclick = () => {
    const promise = new Promise((resolve, reject) => {
        const num = Math.floor(Math.random()*2); // 0 또는 1
        console.log("producing...", num);

        try {
            if(!num) throw new Error("오류 발생!");
            resolve(num); // 이행 콜백 호출
        } catch {
            reject(e);	// 거부 콜백 호출
        }
    });
    console.log(promise);
};

 

홀수 (resolve) 시

state는 'fulfilled'로 변경되었으며, result는 value인 num(1)이 출력되었습니다.

 

짝수 (reject) 시

state는 'rejected'로 변경되었으며, result는 error 객체인 e가 출력 되었습니다.

 

여기까지가 producer 코드이고, 그렇다면 consumer(비동기 완료 후 처리할 코드) 코드는 어떻게 할까요?

resolve (이행) 시 호출할 콜백함수는 then에서 전달하고, reject (거부) 시 호출할 콜백함수는 catch에서 전달해줍니다.

btn1.onclick = () => {
    const promise = new Promise((resolve, reject) => {
        const num = Math.floor(Math.random()*2); // 0 또는 1
        console.log("producing...");

        try {
            if(!num) throw new Error("오류 발생!");
            resolve(num);
        } catch {
            reject(e);
        }
    });
    console.log(promise);

    promise
        .then((value) => {
            console.log('result = ', value);
        })
        .catch((err) => {
            console.error('result = ', err);
        })
};

 

resolve 시

 

reject 시

위처럼 처리되는 것을 확인할 수 있습니다.

 

<단순하게 정리> 

resolve 시 호출할 콜백함수는 .then에서 넘겨준 함수 new Promise(resolve)에 들어가게 되고,

reject 시 호출할 콜백함수는 .catch에서 넘겨준 함수 new Promise(reject)에 들어가게 됩니다.

따라서 정상처리 시 .then에서 넘겨준 함수가 실행되고, 거부 시 .catch에서 넘겨준 함수가 실행됩니다.

또한, resolve/reject의 인자가 선언되어있으므로 인자를 받기 위해 .then/.catch에도 매개변수가 선언되어있는 것이죠.

 


Promise로 setTimeout 작성하기

 

HTML 코드

<button id="btn2">Promise - timeout</button>


Javascript 코드

btn2.onclick = () => {
    getTimeoutPromise()
    .then(() => {
        console.log("1초가 지났습니다.");
    })
};

const getTimeoutPromise = () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, 1000)
    });
};

1초 후 console

getTimeoutPromise() 함수를 정의하였으며, 해당 리턴 값이 Promise 객체이기 때문에 함수 호출 후 then을 이용하여 정상처리 시 함수가 호출되도록 하였습니다.

마찬가지로 new Promise(resolve)에는 then의 함수가 대입되었으며, 1초 후 resolve()로 전달받은 then의 함수를 콜백함수로써 실행하게 되어 1초 후에 "1초가 지났습니다."가 출력됩니다.

 

그렇다면 getTimeoutPromise() 함수 호출 시 타이머 메세지와 시간을 넘겨받아보겠습니다.

btn2.onclick = () => {
    getTimeoutPromise("안녕", 3000)
    .then((value) => {
        console.log(value);
    })
};

const getTimeoutPromise = (msg, millis) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(msg);
        }, millis)
    });
};

3초 후 console

getTimeoutPromise() 함수 호출 시, 타이머 메세지와 시간을 매개인자로 넘겨주게 되고 매개변수로 받아주었습니다.

resolve에 then 함수 (value) => console.log(value)가 대입(단순하게 생각해서), 해당 함수를 콜백함수로써 (resolve(msg))실행하였으므로 resolve의 매개인자 msg가 then (value)로 넘어갔으므로 값이 잘 출력 됩니다.

 

타이머가 실행되고 끝난 후 '실행 끝~'을 출력해보겠습니다.

방법1)

btn2.onclick = () => {
    getTimeoutPromise("안녕", 3000)
    .then((value) => {
        console.log(value);
        console.log('실행끝!');
    })
};

const getTimeoutPromise = (msg, millis) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(msg);
        }, millis)
    });
};

3초 후 console

 

방법2) then은 암묵적으로 Promise 객체를 만들어서 반환 (promise chain)

btn2.onclick = () => {
    getTimeoutPromise("안녕", 3000)
    .then((value) => {
        console.log(value);
        // console.log('실행끝!');
    })
    .then(() => console.log('실행끝!'));
};

const getTimeoutPromise = (msg, millis) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(msg);
        }, millis)
    });
};

3초 후 console

then은 암묵적으로 Promise 객체를 만들어서 반환하므로 다시 then을 이용하여 값을 출력해줄 수 있습니다.

 


Promise Chain

① 암묵적 chain

- then의 resolve 콜백에서 리턴 값이 없다면, 암묵적으로 promise 객체 반환

- resolve 콜백에서 특정 값을 리턴한다면, 암묵적 promise 객체의 result 값으로 사용

 

② 명시적 chain

- then의 resolve 콜백에서 다시 Promise 객체를 반환 후 then 메소드로 chaining 가능 

 

HTML 코드

<button id="btn3">Promise Chain - timeout</button>

 

Javascript 코드

btn3.onclick = () => {
    getTimeoutPromise('안녕', 1000)
    .then((value) => {
        console.log(value);
        return getTimeoutPromise('잘가', 1000)
    })
    .then((value) => console.log(value));
};

const getTimeoutPromise = (msg, millis) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(msg);
        }, millis)
    });
};

return 값이 Promise 객체이므로, 버튼을 누른 후 1초 뒤 '안녕'이 출력되고 1초 뒤 '잘가'가 출력되었습니다.

 

@실습 - 배경색 변경

- .bg-box 배경색을 1초 단위로 빨 → 초 → 노 → 파 → 핑으로 변경

HTML 코드

<button id="btn4">@실습문제 - 배경색변경</button>
<div class="bg-box"></div>

 

CSS 코드

<style>
.bg-box {
    width: 100px;
    height: 100px;
    margin: 10px;
    border: 1px solid black;
}
</style>

 

Javascript 코드

btn4.onclick = () => {
    changeBgcolorPromise('red', 1000)
    .then(() => changeBgcolorPromise('green', 1000))
    .then(() => changeBgcolorPromise('yellow', 1000))
    .then(() => changeBgcolorPromise('blue', 1000))
    .then(() => changeBgcolorPromise('pink', 1000));
};

const changeBgcolorPromise = (color, millis) => {
    const box = document.querySelector(".bg-box");

    return new Promise((resolve) => {
        setTimeout(() => {
            box.style.backgroundColor = color;
            resolve();
        }, millis);
    });
};

 

@실습 - 동적 스크립트 로딩

HTML 코드

<button id="btn5">@실습문제 - 동적 스크립트 로딩</button>

 

1.js

const bar = () => {
    console.log('bar');
    return '../js/2.js';
};

 

2.js

const car = () => {
    console.log('car');
    return "../js/3.js"
};

 

3.js

const dar = () => {
    console.log("dar");
};

 

Javascript 코드

btn5.onclick = () => {
    loadScriptPromise('../js/1.js')
    .then(() => loadScriptPromise(bar()))
    .then(() => loadScriptPromise(car()))
    .then(() => dar())
};

const loadScriptPromise = (path) => {
    return new Promise((resolve) => {
        const script = document.createElement("script");
        script.src = path;
        script.onload = () => {
            console.log(`${path} 로딩완료!`);
            resolve();
        };
        document.head.append(script);
    });
};

 

@실습 - 슬기로운 개발 생활

HTML 코드

<button id="btn6">슬기로운 개발생활</button>

 

Javascript 코드

<script>
const devActions = {
    gotocafe : {
        actionname : '카페로 출발합니다.',
        duration : 1000,
        next : 'arriveatcafe'
    },
    arriveatcafe : {
        actionname : '카페에 도착했습니다.',
        duration : 100,
        next : 'startcoding'
    },
    startcoding : {
        actionname : '코딩을 시작합니다.',
        duration : 3000,
        next : 'finishcoding'
    },
    finishcoding : {
        actionname : '코딩을 마칩니다.',
        duration : 0,
        next : 'gotohome'
    },
    gotohome : {
        actionname : '집으로 출발합니다.',
        duration : 1000,
        next : 'gethome'
    },
    gethome : {
        actionname : '집에 도착했습니다.',
        duration : 0
    },
};

btn6.onclick = () => {
    runDevAction('gotocafe')
    .then((action) => runDevAction(action))
    .then((action) => runDevAction(action))
    .then((action) => runDevAction(action))
    .then((action) => runDevAction(action))
    .then((action) => runDevAction(action))
    .catch((err) => console.error(err))
    .finally(() => {
        // 정상실행 혹은 에러 발생하든 반드시 실행됨
        console.log('해가 지고, 하루가 끝났습니다.');
    })
};

const runDevAction = (action) => {
    const {actionname, duration, next} = devActions[action];
    console.log(actionname);
    return new Promise((resolve) => {
        Math.random() * 100 > 90 && reject(new Error('친구에게 전화가 왔습니다.'));
        setTimeout(() => {
            resolve(next);    
        }, duration);
    });
};

 

에러발생

 

에러미발생

 


Promise chain - 값 반환

 

HTML 코드

<button id="btn7">Promise Chain - 값 반환</button>

 

Javascript 코드

btn7.onclick = () => {
    new Promise((resolve) => {
        resolve(1);
    })
    .then((value) => {
        console.log(value);
        return value * 2; // 암묵적 Promise 객체의 resolve(value)의 result(value)값이 됨
    })
    .then((value) => {
        console.log(value);
        return value * 2;
    })
    .then((value) => {
        console.log(value);
        return value * 2;
    })
};

암묵적으로 생성된 Promise 객체의 resolve(value)는 result값이 되기 때문에 value * 2의 결과 값들이 출력된 것을 확인할 수 있습니다.