13 리팩토링 5 - async & await를 이용한 비동기 처리

source: categories/study/vue-beginner-lv3/vue-beginner-lv3_9-04.md

13.1 자바스크립트 비동기 처리 패턴의 발전 과정

// 개발자가 짜고싶은 비동기 코드의 모습 (현재 async, await를 이용하면 아래와 같은 형식으로 짤 수 있음)
var id = $.get('domain.com/id');
if (id === 'john') {
    var products = $.get('domain.com/products');
}
// 하지만 비동기 코드 작성할 때의 현실 (과거)
$.get('domain.com/id', function (id) {
    if (id === 'john') {
        $.get('domain.com/products', function (products) {
            console.log(products);
        })
    }
})
// promise then 메서드 체이닝
function getId() {
    return new Promise(function (resolve, reject) {
        $.get('domain.com/id', function (id) {
            resolve(id);
        })
    })
}

getId()
    .then(function (id) {
        if (id === 'john') {
            return new Promise(function (resolve, reject) {
                $.get('domain.com/products', function (products) {
                    resolve(products)
                })    
            })
        }
        return new Promise(function (resolve, reject) {
            reject();
        })
    })
    .then(function () {
        
    })
    .catch()

Promise then catch 메소드 체이닝 테스트

function asyncFunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success');
        }, 2000)
    })
}

asyncFunc()
    .then((val) => {
        console.log(val);
    })
    .catch(error => {
        console.log(error)
    })

// success
function asyncFunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success');
        }, 2000)
    })
}

asyncFunc()
    .then(val => {
        console.log(val);
        if (false) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve('second success');
                }, 2000)
            })
        }
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('failed');
            }, 2000)
        })
    })
    .then(val => {
        console.log(val);
    })
    .catch(error => {
        console.log(error)
    })

Promise then catch 메소드 체이닝할 때 이렇게 작성하는게 그나마 깔끔할듯?

function asyncFunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success');
        }, 2000)
    })
}

asyncFunc()
    .then(val => {
        console.log(val);
        return new Promise((resolve, reject) => {
            if (true) {
                setTimeout(() => {
                    resolve('second success');
                }, 2000)
            } else {
                setTimeout(() => {
                    reject('failed');
                }, 2000)
            }
        })
    })
    .then(val => {
        console.log(val);
    })
    .catch(error => {
        console.log(error)
    })

Promise then catch 메소드 체이닝보다 더 깔끔한거 - async await

// 개발자가 짜고싶은 비동기 코드의 모습 (현재 async, await를 이용하면 아래와 같은 형식으로 짤 수 있음)
var id = $.get('domain.com/id');
if (id === 'john') {
    var products = $.get('domain.com/products');
}
console.log(products);

13.2 async & await 문법 소개

async, await는 자바스크립트 비동기 처리 패턴의 최신 문법입니다.
PromiseCallback에서 주는 단점들을 해결하고 자바스크립트의 비동기적 사고 방식에서 벗어나 동기적(절차적)으로 코드를 작성할 수 있게 도와줍니다.

기본 문법

async 함수의 기본 문법은 다음과 같습니다.



async function fetchData() {
    await getUserList();
}


async 함수는 함수의 앞에 async를 붙여주고 함수의 내부 로직 중 비동기 처리 로직 앞에(Promise 인스턴스 반환하는 함수여야함) await를 붙여주면 됩니다.
좀 더 정화갛게 말하면 Promise 객체를 반환하는 API 호출 함수 앞에 await를 붙입니다.

기본 예제



async function fetchData() {
    var list = await getUserList();
    console.log(list);
}

function getUserList() {
    return new Promise(function (resolve, reject) {
        var userList = ['user1', 'user2', 'user3'];
        resolve(userList);
    })
}


fetchData() 함수에서 getUserList() 함수를 호출하고 나면 Promise 객체가 반환됩니다.
그리고 그 Promise는 실행이 완료된 상태(resolve)이며 실행의 결과로 userList 배열을 반환하고 있습니다.
따라서 fetchData()를 호출하면 userList의 배열이 출력됩니다.

13.3 async & await 예제 소개



<template>
    <div>
        <button @click="loginUser">
            login
        </button>
        <h1>List</h1>
    </div>
</template>

<script>
import axios from 'axios';

export default {
    methods: {
        loginUser() {
            axios.get('https://jsonplaceholder.typicode.com/users/1')
                .then(response => console.log(response))
                .catch(error => console.log(error))
        }
    }
}
</script>




<template>
    <div>
        <button @click="loginUser">
            login
        </button>
        <h1>List</h1>
        <ul>
            <li v-for="item in items">{{item}}</li>
        </ul>
    </div>
</template>

<script>
import axios from 'axios';

export default {
    data() {
        return {
            items: [],
        }
    },
    methods: {
        loginUser() {
            axios.get('https://jsonplaceholder.typicode.com/users/1')
                .then(response => {
                    // Promise chaining에 어긋나는 안티패턴이긴 하지만, 이런식으로 비동기 처리를 할 수 있다.
                    if (response.data.id === 1) {
                        console.log('사용자가 인증되었습니다.');
                        axios.get('https://jsonplaceholder.typicode.com/todos')
                            .then(response => {
                                this.items = response.data;
                            })
                            .catch()
                    }
                })
                .catch(error => console.log(error))
        }
    }
}
</script>


13.4 async await 예제 실습

위 예제 코드에 async await 적용해보자



<template>
    <div>
        <button @click="loginUser1">
            login
        </button>
        <h1>List</h1>
        <ul>
            <li v-for="item in items">{{item}}</li>
        </ul>
    </div>
</template>

<script>
import axios from 'axios';

export default {
    data() {
        return {
            items: [],
        }
    },
    methods: {
        loginUser() {
            axios.get('https://jsonplaceholder.typicode.com/users/1')
                .then(response => {
                    // Promise chaining에 어긋나는 안티패턴이긴 하지만, 이런식으로 비동기 처리를 할 수 있다.
                    if (response.data.id === 1) {
                        console.log('사용자가 인증되었습니다.');
                        axios.get('https://jsonplaceholder.typicode.com/todos')
                            .then(response => {
                                this.items = response.data;
                            })
                            .catch()
                    }
                })
                .catch(error => console.log(error))
        },
        // async 함수를 사용하실 때 await 키워드도 꼭 같이 사용해야됩니다. 사용 안하면 의미가 없습니다.
        // async만 작성하고 나중에 babel로 트랜스파일 해보시면 Promise로 비동기 처리하는게 의미가 없어집니다.
        // 항상 비동기 처리 앞에 await를 붙여줘야지 async 함수가 제대로 동작을합니다.
        async loginUser1() {
            const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
            if (response.data.id === 1) {
                console.log('사용자가 인증되었습니다.');
                const list = await axios.get('https://jsonplaceholder.typicode.com/todos');
                this.items = list.data;
            }
        }
    }
}
</script>


13.5 async await 에러 처리 방법과 공통화 함수 작성 방법



<template>
    <div>
        <button @click="loginUser1">
            login
        </button>
        <h1>List</h1>
        <ul>
            <li v-for="item in items">{{item}}</li>
        </ul>
    </div>
</template>

<script>
import axios from 'axios';
import {handleException} from '../utils/hadler.js'

export default {
    data() {
        return {
            items: [],
        }
    },
    methods: {
        loginUser() {
            axios.get('https://jsonplaceholder.typicode.com/users/1')
                .then(response => {
                    // Promise chaining에 어긋나는 안티패턴이긴 하지만, 이런식으로 비동기 처리를 할 수 있다.
                    if (response.data.id === 1) {
                        console.log('사용자가 인증되었습니다.');
                        axios.get('https://jsonplaceholder.typicode.com/todos')
                            .then(response => {
                                this.items = response.data;
                            })
                            .catch()
                    }
                })
                .catch(error => console.log(error))
        },
        // async 함수를 사용하실 때 await 키워드도 꼭 같이 사용해야됩니다. 사용 안하면 의미가 없습니다.
        // async만 작성하고 나중에 babel로 트랜스파일 해보시면 Promise로 비동기 처리하는게 의미가 없어집니다.
        // 항상 비동기 처리 앞에 await를 붙여줘야지 async 함수가 제대로 동작을합니다.
        async loginUser1() {
            // Promise의 then, catch 처리는 네트워크 요청이라던지, 비동기 요청에 대해서만 예외처리를 합니다.
            // 그런데 아래 try catch는 비동기 요청 뿐만아니라 일반적인 자바스크립트 코드 에러까지 같이 예외처리를 할 수 있기 때문에 
            // 훨씬 더 포괄적으로 넓게 에러 처리를 할 수 있습니다.
            // try 안에 있는 구문들의 모든 에러를 catch에서 잡을 수 있습니다.
            try {
                const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
                if (response.data.id === 1) {
                    console.log('사용자가 인증되었습니다.');
                    const list = await axios.get('https://jsonplaceholder.typicode.com/todos');
                    this.items = list.data;
                }
            } catch (error) {
                handleException(error); // 이렇게 에러처리 공통화 함수를 사용할 수도 있다. 아래 예시함수이다.
                console.log(error);
            }
        }
    }
}
</script>




export function handleException(status) {
    // ...
}


13.6 async 함수를 이용한 코드 리팩토링



const actions = {
    async FETCH_LIST({commit}, pageName) {
        try {
            const {data} = await fetchList(pageName);
            commit('SET_LIST', data);
        } catch (error) {
            console.log(error)
        }
    }

    // 프로미스 then 메소드 체이닝을 제대로 이어가려면 프로미스 인스턴스를 return해야된다.
    // FETCH_LIST({commit}, pageName) {
    //     return fetchList(pageName)
    //         .then(({data}) => {
    //             console.log(1);
    //             commit('SET_LIST', data);
    //         })
    //         .catch(error => console.log(error))
    // }
}


async 함수는 어떤걸 return하던간에 Promise 객체로 return합니다.

Note

다시 현재 스피너 및 페이지 전환효과(트랜지션) 작동원리 순서

13.6 트랜지션 효과 수정

  1. /news 라우터로 진입
  2. /news로 가기 전에 네비게이션 가드 beforeEnter 로직 실행
    1. Event bus로 스피너 실행
    2. dispatch로 stateactions에 있는 비동기 데이터 요청 FETCH_LIST 함수 실행
      • FETCH_LIST 함수는 받아온 data를 statebeforeList에 담음
      • beforeList는 각 컴포넌트에 바인딩된 state가 아님
      • 그래서 페이지 컴포넌트가 재렌더링안됨
    3. Event bus로 스피너 종료
    4. next() 함수 실행
  3. next() 실행되고난 후 /news 페이지로 이동중에 페치지 전환효과 transition 발생. 현재 페이지가 페이드아웃함
  4. /news 페이지로 진입하면서 created 라이프사이클 훅 실행
  5. statelist에 업데이트된 beforeList 값이 담김
  6. /news에 새로운 list 값으로 렌더링이 되면서 스르륵 등장함

13.7 [실습 안내] async await 실습 안내

Promise, then, catchasync, await로 전부 수정해보십시오.

13.8 [실습] async await 실습 풀이