[javascript] async and await

Published: by Creative Commons Licence

참고사이트

async & await

프로미스 체이닝을 동기적으로 실행 되는 것처럼 보여질 수 있도록 쓰는 API 기존의 기능을 감싸서 제공하는 API = syntactic sugar

async

async키워드를 function() 앞에 붙이기만 하면 비동기함수가 된다.

async function name([param[, param[, ...param]]]) { statements }

(1) 동기 방식

아래와 같이 동기로 스크립트를 작성할 경우, 순차적인 실행을 하는 자바스크립트 특성상 10초동안 fetchUser()의 실행이 모두 끝날때까지 이후 스크립트는 동작을 할 수가 없어 마냥 기다리게만 된다. 만약 이후 스크립트가 화면을 구성하는 부분이라면 고객은 빈 화면을 10초동안 유지시키고 있어야 하는 불상사가…

function fetchUser() {
    // do network request in 10 secs ...
    return 'macs';
}
const user = fetchUser();
console.log(user);

(2) 비동기 방식 Promise

Promise는 이런 오랜 작업을 비동기로 실행할 수 있게 하고 바로 다음 스크립트를 실행하도록 두 갈래로 갈라주는 역할을 한다.

function fetchUser() {
    return new Promise((resolve, reject) => {
        // do network request in 10 secs..
        // return 'macs';
        /*
        Promise {<pending>}
        __proto__: Promise
        [[PromiseState]]: "pending"
        [[PromiseResult]]: undefined
        -> resolve나 reject로 완료처리를 해 주지 않고 return 'macs'를 하면 계속 pending 상태로 남아 있게 된다.
        */
        resolve('macs');
        /* 서버로부터 응답을 받은 후 resolve를 실행하면 아래와 같이 상태가 변경된다.
        Promise {<fulfilled>: "macs"}
        __proto__: Promise
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: "macs"
        */
    });
}

const user = fetchUser();
user.then(console.log); // macs : Promise 실행 결과가 완료된 후에 로그가 찍히므로 아래 로그보다 더 나중에 찍힘.
console.log(user);

(3) async

함수 선언 앞에 async 키워드만 붙이면 내부적으로 promise로 변경된다.

async function fetchUser() {
    return 'macs'
}
const user = fetchUser();
user.then(console.log);
console.log(user);

(4) await

awaitasync함수 안에서만 쓸수 있고 promise가 끝날때까지 기다리는 기능

[rv] = await expression;

  • expression - Promise를 기다리기 위한 Promise 혹은 어떤 데이터
  • rv - Promise의 결과
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function getApple() {
    await delay(2000);
    // throw 'error'; // 에러를 처리하는 기능 필요
    return '🍎';
}
async function getBanana() {
    await delay(1000);
    return '🍌';
}

Promise를 써서 위 함수들을 호출한다면 아래와 같이 쓸 수 있지만 Promise도 체이닝이 반복되다 보면 콜백지옥과 다를바 없다.

// 콜백지옥이 연상됨 -> 비추
function pickFruits() {
    return getApple().then(apple => {
        return getBanana().then(banana => `${apple} + ${banana}`);
    });
}
pickFruits().then(console.log); // 🍎 + 🍌

위 코드를 async를 사용하면 동기적으로 보이게 코딩할 수 있다.

async function pickFruits() {
    try {
        const apple = await getApple(); // getApple()이 끝날때까지 다음은 실행 못함.
        const banana = await getBanana();
        return `${apple} + ${banana}`;
    } catch {
        // error handling
    }
}
pickFruits().then(console.log); // 🍎 + 🍌

하지만 또 문제가 있는데 그거슨 각 함수들이 병렬적으로 처리가 되지 않아 시간을 많이 잡아먹는다는 것이다. await 덕분에 getApple()(1초) 이 다 실행 된 후 getBanana()(2초)가 실행되어 다 실행될따까지 총 2초가 걸린다. 하지만 getApple()getBanana()는 실행 시 연관이 없는 기능이므로 순차적으로 실행될 필요도 전혀 없다. 아래와 같이 코드를 바꾸면 병렬 실행이 해결된다.

async function pickFruits() {
    const applePromise = getApple(); // promise는 생성되는 순간 실행
    const bananaPromise = getBanana(); // promise는 생성되는 순간 실행되므로 getApple()이 끝날때까지 기다리지 않는다.
    const apple = await applePromise;
    const banana = await bananaPromise;
    return `${apple} + ${banana}`;
}
pickFruits().then(console.log); // 🍎 + 🍌

useful Promise APIs

.all()

Promise를 전달하면 모든 프로미스가 실행된 결과를 모아 배열로 리턴한다. 수동으로 병렬구조를 만들었던 것을 api를 이용해 손쉽게 해결할 수 있다.

function pickAllFruits() {
    return Promise.all([getApple(), getBanana()]).then(fruits => fruits.join(' + '));
}
pickAllFruits().then(console.log); // 🍎 + 🍌

.race()

Promise를 전달하면 먼저 결과가 나온 것만 리턴한다.

function pickOnlyOne() {
    return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log); // 🍌