본문 바로가기

Programming/Javascript

[JavaScript] 비동기, Promise, Async 정리

반응형

[1] Keyword

: 동기, 비동기, Promise, Async, Await

 

# 출처 (아래 출처를 보고 정리한 내용입니다.)

https://elvanov.com/2597

 

 

# 동기

- 동시에 여러 작업을 수행할 수 없다.

- 흐름을 예측하기 쉽다.

 

# 비동기

- 동시에 여러 작업을 수행할 수 있다.

- 흐름을 예측하기 어렵다. (무엇이 먼저 완료될 지 보장 X)

   > 의존성이 길게 이어져 있는 비동기 작업들을 처리할 때 곤혹에 치를 수 있음

 

 

예시 #1

function finishJob(num) {
    console.log(`${num}번 요원의 정보를 받아왔음`);
}

setTimeout(finishJob, 2000, 1);
setTimeout(finishJob, 1500, 2);
setTimeout(finishJob, 1000, 3);

// 결과값

❯ node.exe example.js
3번 요원의 정보를 받아왔음
2번 요원의 정보를 받아왔음
1번 요원의 정보를 받아왔음

 

그럼 어떤 작업들이 비동기로 수행되는 것인가?

- 브라우저에서는 ajax라 불리는 XMLHttpRequest 객체를 활용하여 비동기적으로 요청을 보내고 받을 수 있음
- Fetch API를 사용
- 파일을 다룰 때 쓰는 함수들


[2] Promise

Promise는 비동기 작업의 단위이다.

 

1. 기본 사용법

제일 정석적인 방법은 new Promise(...)로 선언하는 것이다.

const promise1 = new Promise((resolve, reject) => {
    // 비동기 작업 수행
})

 

- new Promise(...)로 Promise 객체를 새로 만들었다.

  생성자는 함수이므로 괄호()를 써서 함수를 호출하는 것과 동일한 모습이다.


- 생성자를 특별한 함수 하나를 인자로 받는다. (이 특별한 함수를 공식 문서에서 executor라고 부른다.)


- Promise는 new Promise(...) 하는 순간 여기에 할당된 비동기 작업은 바로 시작된다.
    ⇒ Promise가 끝나고 난 다음의 동작을 설정해줄 수 있는데, then 메서드와 catch 메서드이다.

 

 

예시 #2

 

- new Promise(...) 키워드로 promiseExample이란 Promise 객체를 만들었다.

 

- Promise는 new Promise(...)하는 순간 비동기 작업은 바로 시작된다.

  => 비동기 작업이기 때문에, 비동기 작업이 끝난 다음의 어떤 작업을 수행할 지 정의할 수 있다.

  => 어떤 작업을 수행할지에 대한 메서드가 바로 then() 메서드와 catch() 메서드이다.

 

- Callback 함수의 인자 resolve를 실행하면 이행(Fulfilled) 상태가 된다. 이행 상태가 되면 아래와 같이 then() 메서드를 이용하여 처리 결과 값을 받을 수 있다.

 

- 콜백 함수 인자로 reject도 사용할 수 있는데, reject를 호출하면 실패(Rejected)로 간주되어 catch() 메서드를 이용하여 에러 결과를 받을 수 있게 된다.

const promiseExample = new Promise((resolve, reject) => {
    resolve();
});

promiseExample
    .then(() => {
        console.log("This is then.");
    })
    .catch(() => {
        console.log("This is catch.");
    });

// 결과값

❯ node.exe example.js
This is then.

출처 : MDN

 

예제 #3

checkAge 함수를 호출할 때 인자 age에 따라 resolve(이행)를 수행할 것인지 reject(거부)를 수행할 것인지 정의할 수 있다.

function checkAge(age) {
    return new Promise((resolve, reject) => {
        if (age > 20) resolve();
        else reject();
    });
}

setTimeout(() => {
    const promise1 = checkAge(25);

    promise1
        .then(() => {
            console.log("promise1 then.");
        })
        .catch(() => {
            console.log("promise1 catch");
        });
    
    const promise2 = checkAge(15);

    promise2
        .then(() => {
            console.log("promise2 then.");
        })
        .catch(() => {
            console.log("promise2 catch.");
        });
}, 1000);

// 결과값

**❯ node example.js**
promise1 then.
promise2 catch.

 

 

예제 #4

resolve, reject 함수에 인자를 전달함으로 then 및 catch 함수에서 비동기 작업으로부터 정보를 얻을 수 있음

function startAsync(age) {
    return new Promise((resolve, reject) => {
        if (age > 20) resolve(`${age} success`);
        else reject(new Error(`${age} is not over 20.`));
    });
}

setTimeout(() => {
    const promise1 = startAsync(25); // 조건에  부합하므로 resolve
    
    promise1
        .then((value) => {
            console.log(value);
        })
        .catch((error) => {
            console.error(error);
        });
    
    const promise2 = startAsync(15);

    promise2
        .then((value) => {
            console.log(value);
        })
        .catch((error) => {
            console.log(error);
        });
}, 1000);

 

 

간단한 원리를 그림으로 나타내기

 

# 요약

 

⇒ Promise는 비동기 작업을 생성/시작하는 부분(new Promise(...))과 작업 이후의 동작 지정 부분(then, catch)을 분리함으로써 기존의 러프한 비동기 작업보다 유연한 설계를 가능토록 한다.

 

⇒ Promise를 만드는 순간 비동기 작업이 시작되며, 비동기 작업을 성공으로 간주하고 싶을 경우 resolve를 호출하고, 실패로 간주하고 싶다면 reject 함수를 호출한다.

 

⇒ 비동기 작업이 성공했을 때의 후속 조치를 지정하고 싶다면 then으로, 실패 시의 후속 조치는 catch로 지정해주면 된다.

 

[3] Async

: 비동기 작업을 만드는 손쉬운 방법

 

async 함수는 Promise와 밀접한 연관이 있는데, 기존에 작성하던 executor로부터 몇 가지 규칙만 적용한다면 new Promise(...)를 리턴하는 함수를 async 함수로 손쉽게 변환할 수 있다.

⇒ 함수에 async 키워드를 붙임
⇒ new Promise ... 부분을 없애고 executor 본문 내용만 남김
⇒ resolve(value); 부분을 return value; 로 변경
⇒ reject(new Error(...)); 부분을 throw new Error(...); 로 수정한다.

 

 

예제 #5

async function startAsync(age) {
    if (age > 20) return `${age} success`;
    else throw new Error(`${age} is not over 20`);
}

setTimeout(() => {
    const promise1 = startAsync(25);
    
    promise1
        .then((value) => {
            console.log(value);
        })
        .catch((error) => {
            console.error(error);
        });
    
    const promise2 = startAsync(15);

    promise2
        .then((value) => {
            console.log(value);
        })
        .catch((error) => {
            console.error(error);
        });
}, 1000);

❯ node example.js
25 success
Error: 15 is not over 20
    at startAsync (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:3:16)
    at Timeout._onTimeout (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:17:22)
    at listOnTimeout (node:internal/timers:557:17)
    at processTimers (node:internal/timers:500:7)

 

위의 코드로부터 알 수 있는 사실
- async 함수의 리턴 값은 무조건 Promise 이다.
    ⇒ promise1, promise2 객체는 async 함수를 실행시킨 뒤 then과 catch를 활용하여 흐름을 제어.
async 함수 안에서는 await 키워드를 사용할 수 있다.

 

 

await : Promise가 끝날 때까지 기다리기
await은 Promise가 이행되었든 거부되었든간에 끝날 때까지 기다리는 함수이다.
await의 제약 조건 : async 함수에서만 사용할 수 있다.

 

 

예제 #6

function setTimeoutPromise(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(), ms);
    });
}

async function startAsync(age) {
    if (age > 20) return `${age} success`;
    else throw new Error(`${age} is not over 20`);
}

async function startAsyncJobs() {
    await setTimeoutPromise(1000);

    const promise1 = startAsync(25);

    try {
        const value = await promise1;
        console.log(value);
    } catch (e) {
        console.error(e);
    }

    const promise2 = startAsync(15);

    try {
        const value = await promise2;
        console.log(value);
    } catch (e) {
        console.error(e);
    }
}

startAsyncJobs();

// Jobs

❯ node example.js
25 success
Error: 15 is not over 20
    at startAsync (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:9:16)
    at startAsyncJobs (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:24:22)

 

1. await은 Promise가 완료될 때까지 기다린다.
    ⇒ setTimeoutPromise의 executor에서 resolve 함수가 호출될 때까지 기다린다.

2. await은 Promise가 resolve한 값을 내놓는다.
    ⇒ async 함수 내부에서는 리턴하는 값을 resolve 한 값으로 간주하므로, value로 ${age} success가 들어오게 된다.

3. 해당 Promise 구문에서 reject가 발생한다면 예외처리가 발생한다.
    ⇒ 이 예외를 처리하기 위해 try-catch 구문이 사용된다.

await은 then과 catch의 동작을 모두 자기 나름대로 처리한다. 그래서 async 함수 내에서 then, catch 메서드의 존재를 잊게 할 수 있다.

 

 

# Promise.all : 여러 비동기 동작을 한꺼번에 기다리기

 

Promise.all : 인자로 Promise의 배열을 받으며, 하나의 특별한 Promise를 새로 생성한다.

 

이 Promise 배열로 받은 모든 비동기 작업이 성공했다면 내부적으로 resolve를 호출하며, 하나라도 비동기 작업이 실패한다면 reject를 호출한다.

 

예제 #7

function setTimeoutPromise(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(), ms);
    });
}
    
    async function fetchAge(id) {
        await setTimeoutPromise(1000);
        console.log(`${id} 사원 데이터 받아오기 완료!`);
        return parseInt(Math.random() * 20, 10) + 25;
    }

async function startAsyncJobs() {
    let promises = [];

    for (let i = 0; i < 10; i++) {
        promises.push(fetchAge(i));
    }

    let ages = await Promise.all(promises);

    console.log(`평균 나이는? ==> ${ages.reduce((prev, current) => prev + current, 0) / ages.length}`);
}

startAsyncJobs();

// 결과값

❯ node example.js
0 사원 데이터 받아오기 완료!
1 사원 데이터 받아오기 완료!
2 사원 데이터 받아오기 완료!
3 사원 데이터 받아오기 완료!
4 사원 데이터 받아오기 완료!
5 사원 데이터 받아오기 완료!
6 사원 데이터 받아오기 완료!
7 사원 데이터 받아오기 완료!
8 사원 데이터 받아오기 완료!
9 사원 데이터 받아오기 완료!
평균 나이는? ==> 36.7

 

 

[4] Async와 Promise 요약 정리

- Promise는 JavaScript에서 비동기 처리에 사용되는 객체이며 resolve와 reject를 통해 콜백의 성공/실패 케이스를 작성할 수 있다.

 

- Await은 Promise 기반으로 동작한다. 비동기 코드를 async, await을 통해 가독성을 높여주며 예외 처리는 try - catch를 통해 수행할 수 있다.

 

- DB로부터 데이터를 가져와야 하는데 사용자에게는 지속적으로 서비스를 제공해야 할 경우 특정 메서드를 비동기로 가져와야 하기 때문에 이럴 때 async - await 구문을 사용할 수 있다고 보면 된다.

 

 

에제 #8 - Promise

처음 then에서 resolve하여 다음 then에서 인자로 받도록 하기 위해 아래와 같이 Code를 구현할 수 있다.

const condition = true;

const promise = new Promise((resolve, reject) => {
    if (condition) {
        resolve('success');
    } else {
        reject("fail");
    }
});

promise
    .then((message) => {
        console.log(`첫 번째 then 이며, message : ${message}를 전달 받았습니다.`);
        return new Promise((resolve, reject) => {
            resolve(message);
        });
    })
    .then((message2) => {
        console.log(`두 번째 then이며, mssage2 : ${message2}를 전달 받았습니다.`);
        return new Promise((resolve, reject) => {
            resolve(message2);
        });
    })
    .then((message3) => {
        console.log(`세 번째 then이며, message3 : ${message3}를 전달 받았습니다.`);
    })

    .catch((error) => {
        console.error(`error 내용 : ${error}`);
    });

**// 결과값

❯ node 비동기/example.js

첫 번째 then 이며, message : success를 전달 받았습니다.
두 번째 then이며, mssage2 : success를 전달 받았습니다.
세 번째 then이며, message3 : success를 전달 받았습니다.**

 

예제 #9 - Async, await

 

user 객체를 DB로부터 가져오기 위해서 async - await 구문을 통해 비동기를 구현할 수 있다.

async function findAndSaveUser(Users) {
    let user = await Users.findOne({});

    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender: 'm' });
    // 생략
}

 

예제 #10 - Async, await 예외 처리

try - catch로 예외 처리를 수행할 수 있다.

async function findAndSaveUser(Users) {
    try {
        let user = await Users.findOne({});

        user.name = 'zero';
        user = await user.save();
        user = await Users.findOne({ gender: 'm' });

        // 생략
    } catch (error) {
        console.error(error);
    }
}

 

 

 

 

 

## 출처 

https://elvanov.com/2597

 

반응형

'Programming > Javascript' 카테고리의 다른 글

[Javascript] 반복문 정리  (0) 2021.11.14