Web/HTML CSS JS

Java Script - 비동기 프로그래밍 1 (callback, promise)

dev_sr 2020. 9. 25. 22:52
 

2020.07.13. 수업내용 - node.js (1)

참고할 사이트 1. node.js 다큐먼트 Index | Node.js v13.14.0 Documentation nodejs.org 2. java script 다큐먼트 JavaScript JavaScript(JS)는 가벼운 인터프리터 또는 JIT 컴파일 프로그래밍 언어로, 일급 함수..

srk911028.tistory.com

 

다시 복습~

 

자바 스크립트는 동기적 언어(synchronous)

 

동기 방식(sync)

hoisting이 된 이후부터 순차적으로 한줄 한줄 실행

(hoisiting: var, function 선언을 가장 위로 올려주는 것)

한 메서드가 오래걸려도 계속 기다리고 완료되면 다음 메서드 실행 (블로킹)

 

 

 

비동기 방식(async) 

안기다리고 먼저 실행(논블로킹)

console.log('1');
setTimeout(function () { //browser API 1초 있다가 콜백함수 실행해
    console.log('2');
}, 1000);
console.log('3');

 

 

 

callback 

setTimeout(function () { //browser API 1초 있다가 콜백함수 실행해
    console.log('2');
}, 1000);

setTimeout에서 전달되는 function 부분  -> 나중에 이 함수를 다시 call 해줘! -> callback

 

1. 동기적 콜백

function printImmediately(print) {
    print();
}
printImmediately(() => console.log('hello'));

 

2. 비동기적 콜백

function printWithDelay(print, timeout) {
    setTimeout(print, timeout);
}
printWithDelay(() => console.log('async callback'), 2000);

 

호이스팅까지 된 전체 코드 순서

'use strict';

//호이스팅으로 함수 선언 부분은 가장 위로 올라온다.
//동기적 콜백
function printImmediately(print) {
    print();
}

//비동기적 콜백
function printWithDelay(print, timeout) {
    setTimeout(print, timeout);
}

//1번 실행 (동기)
console.log('1');
//2번 실행 (1초 기다렸다 출력해) (비동기)
setTimeout(function () { //browser API 1초 있다가 콜백함수 실행해
    console.log('2');
}, 1000);
//3번 실행 -2번 안기다리고 출력 (동기)
console.log('3');
//4번 실행 -2번 안기다리고 출력 (동기)
printImmediately(() => console.log('hello'));
//2번 출력 (1초 지남)
//4번 출력 후 2초 있다가 출력 (비동기)
printWithDelay(() => console.log('async callback'), 2000);

 

 

 

 

콜백 지옥 

가독성 지옥

유지보수가 어려움

비지니스 로직을 이해하기 어려움

class UserStorage {
    loginUser(id, password, onSuccess, onError) {
        setTimeout(() => {
            if ((id === 'cat' && password === '1234') ||
                (id === 'rabbit' && password === '5678')) {
                onSuccess(id); //onSuccess 콜백실행
            } else {
                onError(new Error('not found')); //onError 콜백 실행
            }
        }, 2000);
    }

    getRoles(user, onSuccess, onError) {
        setTimeout(() => {
            if (user === 'cat') {
                onSuccess({ //onSuccess 콜백실행
                    name: '야옹이',
                    role: 'admin'
                });
            } else { //onError 콜백 실행
                onError(new Error('no access'));
            }
        }, 1000);
    }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
    id,
    password,
    (user) => { //onSuccess(id)
        userStorage.getRoles(
            user,
            (userWithRole) => {
                alert(`Hello! ${userWithRole.name}, you are ${userWithRole.role}`);
            },
            (error) => {
                console.log(error);
            });
    },
    (error) => { //onError
        console.log(error)
    });

 

2초 있다 나옴

 

 

 

 

Promise

콜백 함수 대신에 비동기 프로그래밍을 도와주는 java script에서 제공하는 오브젝트

시간이 오래걸리는 기능들은 Promise를 이용하여 비 동기적으로 처리하는 것이 좋다! (network, read files)

1. state

pending : operation을 수행 중일 때  

fulfilled : operation을 성공적으로 완료

rejected : 파일을 찾을 수 없거나 네트워크 오류

 

2. producer objector

원하는 기능을 수행해서 원하는 데이터를 만들어내는 promise object

콜백함수로 executor를 전달하는데, executor에는 resolve와 reject 콜백 함수가 있다.

//1.producer
const promise = new Promise((resolve, reject) => {
    //executor 
    //=> promise를 만드는 순간 바로 executor가 자동 실행, 네트워크 통신이 일어남
    //버튼액션 등엔 어울리지 않음
    //resolve 콜백 :정상수행 이후 데이터 전달
    //reject 콜백: 문제가 생겼을 때 호출  

    setTimeout(() => {
        resolve('야옹이'); //성공적인 통신을 마치고 가공된 데이터 '야옹이'전달
    }, 2000);

});

 

3. consumer objector

원하는 데이터를 소비하는 promise object

then, catch, finally를 사용

//2. consumers: then, catch, finally
//기능이 정상적으로 수행이 된다면 내가 value값을 가져와서 기능을 수행할거야
promise.then((value) => {
    console.log(value); //2초있다 야옹이 출력
})

 

 

producer에서 날아온 값이 reject라면

//1.producer
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('no network'));
    }, 2000);
});

 

에러가 잡히지 않는다고 나온다.

error handler 가 필요하므로 consumer에 .catch를 연결해준다

(then이 다시 promise를 반환하기 때문에 chaining으로 쓸 수 있다)

promise
    .then((value) => { //resolve 일 때 데이터 다루기
        console.log(value); //2초있다 야옹이 출력
    })
    .catch(error => { //reject 시 에러 핸들링
        console.log(error);
    });

2초 있다가 작성한 에러 내용이 제대로 나온다.

 

 

 

finally는 성공, 실패 상관없이 무조건 실행된다.

promise
    .then((value) => { //resolve 일 때 데이터 다루기
        console.log(value); //2초있다 야옹이 출력
    })
    .catch(error => { //reject 시 에러 핸들링
        console.log(error);
    })
    .finally(() => { // 성공 실패 상관없이 무조건 마지막에 출력
        console.log('finally')
    });

 

 

 

4. promise 연결

then은 값을 전달할 수도 있고 promise를 전달해도 된다.

const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
});

fetchNumber
    .then(num => num * 2) //2
    .then(num => num * 3) //6
    .then(num => {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(num - 1), 1000); //5
        });
    })
    .then(num => console.log(num)); //5

 

 

5. error handling

닭->달걀->달걀 후라이를 해주는 promise를 만듦

const getChicken = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('닭'), 1000);
});

const getEgg = (chicken) => new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${chicken} => 달걀`), 1000);
})

const cook = (egg) => new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 달걀 후라이~`), 1000);
});

getChicken()
    .then((chicken) => getEgg(chicken))
    .then((egg) => cook(egg))
    .then(meal => console.log(meal));

//함수 전달시 동일한 값을 그대로 다시 전달할 때 줄여서 쓸수도 있다.
getChicken()
    .then(getEgg)
    .then(cook)
    .then(console.log);

 

 

계란 받아오기가 실패한다면?

const getChicken = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('닭'), 1000);
});

const getEgg = (chicken) => new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${chicken} => 달걀`)), 1000);
})

const cook = (egg) => new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 달걀 후라이~`), 1000);
});

getChicken()
    .then(getEgg)
    .then(cook)
    .then(console.log)
    .catch(console.log);

이렇게 에러처리를 할 수도 있지만

프로그램(promise chain)에 문제가 되지 않게 오류를 잘 처리할 수 있다.

getChicken()
    .then(getEgg)
    .catch(error => {
        return '오리알!';
    })
    .then(cook)
    .then(console.log)
    .catch(console.log);

 

 

 

6. 콜백 지옥 -> promise

먼저 아까 작성한 콜백지옥

class UserStorage {
    loginUser(id, password, onSuccess, onError) {
        setTimeout(() => {
            if ((id === 'cat' && password === '1234') ||
                (id === 'rabbit' && password === '5678')) {
                onSuccess(id); //onSuccess 콜백실행
            } else {
                onError(new Error('not found')); //onError 콜백 실행
            }
        }, 2000);
    }

    getRoles(user, onSuccess, onError) {
        setTimeout(() => {
            if (user === 'cat') {
                onSuccess({ //onSuccess 콜백실행
                    name: '야옹이',
                    role: 'admin'
                });
            } else { //onError 콜백 실행
                onError(new Error('no access'));
            }
        }, 1000);
    }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
    id,
    password,
    (user) => { //onSuccess(id)
        userStorage.getRoles(
            user,
            (userWithRole) => {
                alert(`Hello! ${userWithRole.name}, you are ${userWithRole.role}`);
            },
            (error) => {
                console.log(error);
            });
    },
    (error) => { //onError
        console.log(error)
    });

 

promise로 정리한 콜백지옥

class UserStorage {
    loginUser(id, password) {
        return new Promise((resolve, reject) => {
            if ((id === 'cat' && password === '1234') ||
                (id === 'rabbit' && password === '5678')) {
                resolve(id);
            } else {
                reject(new Error('not found'));
            }
        }, 2000);
    }

    getRoles(user) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (user === 'cat') {
                    resolve({
                        name: '야옹이',
                        role: 'admin'
                    });
                } else {
                    reject(new Error('no access'));
                }
            })
        }, 1000);
    }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');

userStorage
    .loginUser(id, password)
    .then(userStorage.getRoles)
    .then(user => alert(`Hello! ${user.name}, you are ${user.role}`))
    .catch(console.log);

 

 

 

 

www.youtube.com/watch?v=JB_yU6Oe2eE&list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&index=12