동기와 비동기(callbac, promise, async)
본문 바로가기

Frontend/모던자바스크립트

동기와 비동기(callbac, promise, async)

동기와 비동기

자바스크립트는 원래 Synchronous동기이다. 예를들어

console.log(1)
console.log(2)
console.log(3)

이 코드를 실행시켜보면 호이스팅이후에 한번에 한줄로 순서대로 진행되어 1,2,3이 출력된다.

 

setTimeout은 브라우저에서 제공되는 API로 브라우저에게 요청하며 몇초후에 콜백함수를 실행시킬수있다.

이외에도 ajax함수나 EventListener등이 콜백함수를 실행시키는데 콜백함수로 동기처리와 비동기처리Asynchronous둘다 가능하지만 보통 비동기처리하는데 주로 사용한다!!!

 

비동기의 필요성

예를들어 10초뒤에 데이터를 가져온다고하면 동기일경우 10초동안 아무것도못하고 로딩상태에 빠지게된다.

이를 비동기처리를 해주면 10초동안 대기실로 이동한뒤 해결할동안 다른 로직들을 처리할수있다.

[애플코딩] setTimeout은 대기실로 잠깐이동했다가 0초뒤에 Queue로 옮겨서 stack이 비워질때까지 대기

 

콜백지옥

아래와 같은 콜백지옥이 있다고하고 이를 비동기처리코드로 깔끔하게 바꿔보자.

id와 pw를 판별하여 그 아이디의 존재여부를 판단해 존재한다면 name과 role을 알려주는 코드이다.(하단의 드림코딩 강의 참고)

콜백지옥은 가독성과 비즈니스로직분석,파악이 힘들고 디버깅이 힘들다. => 유지보수의 어려움

        //callback hell
        class UserStorage{
            loginUser(id, pw, onSuccess, onError){
                setTimeout(()=>{
                    if(
                        (id === 'ellie' && pw === 'dream')
                    ){
                        onSuccess(id)
                    }else{
                        onError(new Error('not found'))
                    }
                },2000)//네트워크 통신이 대략 2초라고 가정
            }
            getRoles(user, onSuccess, onError){ //사용자의 관련 정보 원래는 백엔드에서 처리
                setTimeout(()=>{
                    if(user ==='ellie'){
                     onSuccess({name:'ellie', role:'admin'})   
                    }else{
                        onError(new Error('no access'))
                    }
                })
            }
        }

        const userStorage = new UserStorage()
        const id = prompt('enter ID')
        const pw = prompt('enter PAWWARD')
        userStorage.loginUser(id,pw,(user) =>{
            userStorage.getRoles(user,(userGetRole)=>{
                alert(`hello ${userGetRole.name}. you have a ${userGetRole.role} role`)
            }, (err)=>{
                console.log(err)
            })
        },(err)=>{
            console.log(err)
        })

 

비동기처리 1. promise

promise는 자바스크립트 object로 비동기 처리를 할 수있게 도와준다. 성공resolve하면~를 전달하고 실패reject하면 에러를 전달해준다.

//promise object 선언
const promise = new Promise((resolve, reject)=>{
	console.log('doing something...') 
    //heavy logic:network, readfiles
    setTimeout(()=>{
    	//resolve('ellie')
        reject(new Error('no network'))
    },2000)
})

promise
.then((value)=>{
	//정상적으로 잘될경우만 작동(resolve콜백함수에 파라미터를 전달)
	console.log(value)
})
.catch(err => {
	//에러가 발생할때 작동(reject콜백함수에 자바스크립트에서 제공하는 Error객체 전달)
	console.log(err)
})
.finally(()=>{
	console.log('finally')//성공하던 실패하던 무조건 끝에 출력
})

참고로 then은 promise그 자체를 리턴하기때문에 catch는 promise의 에러를 다룰 수있다.

주의할점!! 새로운 프로미스가 생성되면 무조건 실행 되기때문에 불필요한 네트워크 통신이 이루어질 수 있다.

[드림코딩] promise의 예시

참고로 then등으로 넘길때 값들이 같으면 화살표함수와 (파라미터)들의 암묵적인 생략이가능하다!!!

getHen()
.then(getEgg)
.then(cook)
.tnen(console.log)

promise에러핸들링

만약 egg에서 문제가 생길때 에러처리를 통해 빵으로 변환할 수있다.

[드림코딩] then 바로다음에 catch를해서 에러를 처리할 수있음.

이제 콜백지옥에 빠졌던 로그인 로직을 promise로 코드개선해보자!

        class UserStorage {
            loginUser(id, pw) {
                return new Promise((resolve, reject) =>{
                    setTimeout(() => {
                        debugger;
                        if (id === "ellie" && pw === "dream") {
                            resolve(id);
                        } else {
                            reject(new Error("not found"));
                        }
                    }, 2000); //네트워크 통신이 대략 2초라고 가정
                })
            }
            getRoles(user) {
                //사용자의 관련 정보 원래는 백엔드에서 처리
                return new Promise((resolve, reject)=>{
                    setTimeout(() => {
                        if (user === "ellie") {
                            resolve({ name: "ellie", role: "admin" });
                        } else {
                            reject(new Error("no access"));
                        }
                    },2000);
                })
            }
        }
        
        const userStorage = new UserStorage();
        const id = prompt("enter ID");
        const pw = prompt("enter PAWWARD");

        userStorage
        .loginUser(id,pw)
        .then(userStorage.getRoles)
        .then(user => alert(`hi, ${user.name}`))
        .catch(console.log)

 

비동기처리 2. async await

프로미스의 체이닝의 단점을 해결한방법으로 무조건 답은 아니다.

 

아래 pickFruit()함수는 바나나를 받으면 비로소 애플을 출력하는 코드이다.

promise로 할경우 가독성이 떨어진다.

        function delay(ms){
            return new Promise(resolve => setTimeout(resolve, ms))
        }
        async function getApple(){
            await delay(1000)
            return 'apple'
        }
        async function getBanana(){
            await delay(1000)
            return 'banananan'
        }

        function pickFruit(){
            return getApple()
            .then(result1 =>{
                return getBanana()
                .then(result2 => `${result1}+${result2}`)
            })
        }
        pickFruit().then(console.log)

이를 async await을 활용한다면

async function pickFruit(){
    const result1 = await getApple()//1초걸림
    const result2 = await getBanana()//1초걸림
    return `${result1}+${result2}`
}//총 2초걸림

에러핸들링은 try catch로 감싸서 처리한다.

 

병렬처리 promiseAll

방금 2초나 걸린이유는 getApple이 끝날때까지 기다렸다가 끝난이후에 getBanana를 실행했기 때문이다.

동시에 시작시키고싶다면 앞서 promise에 주의해야하는 점이었던 promise를 선언과 동시에 실행된다는 점을 이용할 수있다.

async function pickFruit(){
	const applePromise = getApple()//프로미스로 만들어지면서 바로실행
    const bananaPromise = getBanana()//프로미스로 만들어지면서 바로실행
    const result1 = await applePromise
    const result2 = await bananaPromise
    return `${result1}+${result2}`
}//1초걸림

하지만 이방법은 좋은방법이 아니라고하며

promiseAll과 같은 promise에서 제공하는 API를 활용하는 방법이 있다.

function pickAllFruits(){
    return Promise.all([getApple(), getBanana()])
    .then(results => results.join(' + '))
}
pickAllFruits(console.log)

이제 로그인로직을 async, await로 바꿔보자

        class UserStorage {
            delay(ms){
                return new Promise((resolve) => setTimeout(resolve, ms))
            }
            async loginUser(id, pw) {
                await this.delay(1000)
                if (id === "ellie" && pw === "dream") {
                    return id;
                } else {
                    throw "not found";
                }
            }
            async getRoles(user) {
                await this.delay(2000)
                if (user === "ellie") {
                    return { name: "ellie", role: "admin" };
                } else {
                    throw "no access";
                }
            }
        }
        const userStorage = new UserStorage();
        const id = prompt("enter ID");
        const pw = prompt("enter PAWWARD");

        async function findUserRole(){
            const user = await userStorage.loginUser(id,pw)
            const result = await userStorage.getRoles(user)
            return `${result.name}!!!!`
        }

        findUserRole()
        .then(console.log)
        .catch(console.log)

https://www.youtube.com/watch?v=s1vpVCrT8f4 

 

https://www.youtube.com/watch?v=v67LloZ1ieI&t=556s 

 

반응형