본문 바로가기
TIL with Programmers

[TIL] 10/14 Node.js 비동기 처리 - Promise, async, await, then, query

by 보먀 2024. 10. 14.
728x90
반응형

1. Node.js 비동기 처리 방식

 

- 비동기 발생

실행되는 코드가 기다려야하는 시간이 생긴다는 의미

(이전 작업이 오래 걸리면 기다려주지 않고 다음 코드를 무작정 실행)

 

ex) setTimeOut(), setInterval(), query() ...

 

- 비동기 처리

이전 작업의 시간을 다 기다려서 순서를 맞춰서 코드를 실행

 

1. 콜백 함수: 할 일 다하고, 콜백함수 실행 (= 순서 맞춰서 뒤에 실행)

2. promise (resolve, reject)

3. then & catch

4. ES2017 promise => async & await

 

 

1.1. Promise

let promise = new Promise(function(resolve, reject) {
    // ...
});

 

 

Promise 는 resolve, reject 를 매개변수로 가지고 있는데, 이 매개변수들은 Promise 의 할 일이 끝난 뒤 불러 줄 콜백 함수이다.

 

Promise 에 마우스를 올려놓으면 위와 같이 뜨는데, executor 는 Promise 가 할 일(지켜내야 할 약속)이고, Promise 가 할 일을 성공적으로 끝내면 resolve 를, 할 일을 실패하면 reject 를 호출한다. 

 

 resolve/reject는 일이 끝나면 무조건 resolve/reject 를 호출해서 함수 안에 값을 넣어 전달한다. 

  • 성공 -> resolve(결과)
  • 실패 -> reject(에러)

 

1.2. then()

 

then 메서드는 Promise 가 완료되었을 때 실행되는 콜백을 설정하는 메서드로 2개의 콜백 함수를 받는다. 

첫 번째 콜백 함수는 작업이 성공했을 때, 즉 resolve 가 사용될 때 호출되고, 

두 번째 콜백 함수는 작업이 실패했을 때, 즉 reject 가 사용될 때 호출된다. 

 

이 예시 코드를 살펴보자.

 

3초 후 resolve("완료") 가 호출되어 promise 가 완료된다. 성공적으로 일을 끝냈기 때문에 then 의 첫 번째 콜백 함수의 result 로 결과인 "완료!" 가 들어가게 되고 콜백 함수가 실행되어 "완료!" 가 콘솔에 출력된다. 

let promise = new Promise(function(resolve, reject) {
    setTimeout(() => resolve("완료!"), 3000); // 할 일
});

// Promise의 기본 메서드 then: promise가 일 다 하고(= 약속 다 지키고) 호출해야하는 함수
promise.then(
    function(result) {
        console.log(result);
    }, 
    function(error) {
        console.log(error);
    }
);

 

 

1.3. promise chaining 

 

여러 개의 비동기 작업을 순차적으로 처리할 때 하나의 Promise 가 완료된 후 다음 Promise 를 이어서 처리하는 방식이다. 

각 then 메서드는 새로운 Promise 를 반환하기 때문에, 이를 이용해 연속적으로 비동기 작업을 처리할 수 있다. 

let promise = new Promise(function(resolve, reject) {
    setTimeout(() => resolve("완료"), 3000);
}).then(
    function(result) {
        console.log(result);
        return result + "!!!!!"; // 다음 then 메서드의 result로 들어감
    }, 
    function(error) {}
).then(
    function(result) {
        console.log(result);
        return result + "!!!!!"; // 다음 then 메서드의 result로 들어감
    }, 
    function(error) {}
).then(
    function(result) {
        console.log(result);
        return result + "!!!!!"; // 다음 then 메서드의 result로 들어감
    }, 
    function(error) {}
);

 

출력

 

 

1.4. async & await

 

async 는 자바스크립트에서 비동기 함수를 정의할 때 사용하는 키워드로, 함수 앞에 async 키워드가 붙으면 무조건 Promise 객체를 반환한다. 

  • 반환 값이 Promise 가 아닐 때 -> Promise.resolve 로 감싼 객체 반환
  • 반환 값이 Promise 일 때 -> Promise 객체 반환

아래의 코드를 보면 return 7 로 7을 반환하는 것 같지만, 내부에서 자동으로 Promise 객체를 만들어서 7을 감싸서 Promise.resolve(7) 로 반환한다. 

async function f() {
    return 7; // Promise 객체 반환 중
}

f().then(
    function(result) {
        console.log("resolve: ", result);
    },
    function(err) {
        console.log("reject: ", err);
    }
)

 

 

async 는 Promise 객체를 만들어주는 역할도 하지만, await 과 함께 사용되었을 때는 Promise 객체가 일이 끝날 때까지 기다릴 수 있는 공간을 제공하는 역할도 한다. 

await 은 async 안에서만 사용할 수 있으며, promise 객체가 일을 다 할 때까지 기다리도록 해준다. 

 

아래의 코드는 위에 코드와 동일한 기능을 하는데, async + then 이렇게 사용했을 때보다 직관적이고 가독성이 좋다. 

async function f() {
    let promise = new Promise(function(resolve, reject) {
        setTimeout(() => resolve("완료!"), 3000);
    });

    let result = await promise; // promise 객체가 일 다 할때까지 기다려줌
    console.log(result);
}

f();

 

 

 

3개의 promise 순서대로 실행시키기

async function f() {

    let promise1 = new Promise(function(resolve, reject) {
        setTimeout(() => resolve("첫 번째 쿼리"), 3000);
    });

    let result1 = await promise1;
    console.log(result1);

    let promise2 = new Promise(function(resolve, reject) {
        setTimeout(() => resolve("두 번째 쿼리 with " + result1), 3000);
    });

    let result2 = await promise2;
    console.log(result2);

    let promise3 = new Promise(function(resolve, reject) {
        setTimeout(() => resolve("세 번째 쿼리 with " + result2), 3000);
    });

    let result3 = await promise3;
    console.log(result3);
}

f();

 

 

 

2. query 문 순서대로 실행시키기

 

주문 API 의 "주문하기" 기능을 구현하다가 문제가 생겼다.

 

리퀘스트 바디에 담겨온 정보 중 배송 정보인 delivery 와 구매하는 책 정보인 items 배열은 각각 delivery 테이블과 orderedBook 테이블에 저장해서 관리하는데, 참조 관계 때문에 INSERT 순서가 존재한다. 

 

먼저 delivery 정보를 delivery 테이블에 INSERT 하고 delivery_id 값을 들고 나오고, 들고 나온 delivery_id 값은 주문 정보를 orders 테이블에 INSERT 할 때 사용된다. orders 테이블에 INSERT 하면서 order_id 를 들고 나오고 들고 나온 order_id 값은 주문 도서 정보를 orderedBook 테이블에 INSERT 할 때 사용된다. 

 

테스트를 해보니 첫번째 쿼리문이 실행되고 나서 delivery_id 값이 undefined 로 찍히는 이상한 문제가 생겼는데,

query 문이 비동기로 처리되기 때문에 쿼리문 실행이 끝나서 delivery_id 값에 올바른 값이 들어오기 전에 다음 쿼리문이 실행되어서 발생하는 문제였다. (쿼리문이 순서대로 실행되지 않아서 발생하는 문제) 그렇다면 문제를 고쳐보자. (다음 편에..)

 

자세한 내용 참고 ▼

 

[TIL] 10/11 주문 기능 구현

1. 주문 기능 구현 1.1. 결제(주문) API - 결제하기 = 주문하기 = 주문등록 = 데이터베이스 주문 INSERT items -> 결제할 도서가 담겨있는 배열delivery -> 입력받은 주소/이름/연락처 firstBookTitle -> 주문 목

everydayc0ding.tistory.com

 

const order = (req, res) => {
    const {items, delivery, totalQuantity, totalPrice, userId, firstBookTitle} = req.body;

    let delivery_id = '';
    let order_id = '';

    let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
    let values = [delivery.address, delivery.receiver, delivery.contact];
    conn.query(sql, values,
        (err, results) => {
            if (err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            delivery_id = results.insertId;
        }
    )

    sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
            VALUES (?, ?, ?, ?, ?)`
    values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id]
    conn.query(sql, values,
        (err, results) => {
            if (err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            order_id = results.insertId;
        }
    )
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?` // 벌크로 insert 를 한다

    values = [];
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    })
    conn.query(sql, [values],
        (err, results) => {
            if (err) {
                console.log(results);
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.OK).json(results);
        }
    )
};

 

728x90
반응형