본문 바로가기
전공수업/웹서버프로그래밍(Node.js)

[JavaScript] Callback Hell, Promise, async/await

by JooRi 2024. 5. 1.
728x90
반응형
SMALL

* Callback hell

프로미스를 알기 위해서는 콜백 헬을 알아야 한다.

 

콜백 헬은 비동기적으로 동작하는 자바스크립트 코드에서 나타나는 문제 중 하나로, 콜백 안에 콜백을 사용하는 형식이다.

비동기 작업 시 콜백함수를 사용하는데, 콜백함수가 중첩되면 코드가 복잡해지고 가독성이 떨어지는 것을 Callback hell이라고 한다.

 

(콜백함수와 비동기식 작업은 이전 글에 정리해 두었다.)

2024.03.18 - [Back-End/Node.js] - Node.js의 정의, 특성, 역할

 

콜백 헬을 해결하는 방법에는 Promise(프로미스)와 Async/wait이 있다.

 

* Promise(프로미스)

콜백 헬을 해결하는 방법 중 하나로, 자바스크립트 비동기처리에 사용되는 객체이다.

 

프로미스는 비동기 작업이 성공적으로 수행되면 값을 생성하고, 시간초과나 네트워크 오류 등으로 인해 실패하면 에러를 생성한다.

따라서 프로미스는 비동기 작업 성공 또는 실패의 상태 정보를 갖는다.

 

resolve와 reject에 넣어준 리턴 값은 각각 then과 catch의 매개변수에서 받는다.

resolve(성공 시 리턴값) -> then 메서드로 연결

reject(실패 시 리턴값) -> catch 메서드로 연결

 

결론

프로미스는 실행은 하되 결괏값은 나중에 받는 객체이다. 결괏값은 실행 완료 후 then이나 catch 메서드로 받는다.

resolve가 호출되면 then 실행, reject가 호출되면 catch 실행.

 

프로미스 사용 예시

const condition = true;  // 프로미스가 성공할지 실패할지 결정하는 condition 변수, true면 resolve, false면 reject
const promise = new Promise((resolve, reject) =>// Promise 객체 생성
    if (condition){  // condition이 true인 경우
        resolve('성공');  // resolve 함수 호출
    } else{  // condition이 false인 경우
        reject('실패');  // reject 함수 호출
    }
});

promise
.then((message) => {  // 성공(resolve)한 경우 then 실행
    console.log(message);  
})
.catch((error) => {  // 실패(reject)한 경우 catch 실행
    console.error(error);
});
 
// 출력
성공

 

resolve('성공')이 호출되면 then의 message가 '성공'이 되고,

reject('실패')가 호출되면 catch의 error가 '실패'가 된다.

 

 

Promise.all - 프로미스 여러 개를 한 번에 실행 가능

프로미스가 여러 개 있을 때 Promise.all에 넣으면 모두 resolve 될 때까지 기다렸다가 then으로 넘어간다.

 

Promise.all 사용 예시

const promise1 = Promise.resolve('성공1');  // 성공1을 이행하는 프로미스 생성
const promise2 = Promise.resolve('성공2');  // 성공2를 이행하는 프로미스 생성

Promise.all([promise1, promise2])  // promise1과 promise2가 모두 이행될 때까지 기다림
.then((result) => {  
    console.log(result);
})
.catch((error) => {
    console.error(error);
});
 
// 출력
(2) ['성공1', '성공2']

 

* 프로미스 체이닝(Promise Chaining)

then이나 catch에서 다시 다른 then이나 catch를 붙여서 연달아 사용 가능하다.

이전 then의 return 값을 다음 then의 매개변수로 넘긴다.

 

프로미스 체이닝 사용 예시

promise
.then((message) => {   // 프로미스 객체의 then() 메소드 호출, 첫번째 작업 실행
    // 첫번째 작업에서 받은 message를 그대로 반환하는 새로운 프로미스 생성
    return new Promise((resolve, reject) => {  
        // 첫번째 작업에서 받은 message를 이행값으로 하여 프로미스 이행
        resolve(message);  
    });
})

// 첫번째 작업의 이행값을 받아와서 두번째 작업 실행
.then((message2) => {
    // 두번째 작업에서 받은 message2 출력
    console.log(message2);
    // 두번째 작업에서 받은 message2를 그대로 반환하는 새로운 프로미스 생성
    return new Promise((resolve, reject) => {
        // 두번째 작업에서 받은 message2를 이행값으로 하여 프로미스 이행
        resolve(message2);
    });
})

// 두번째 작업의 이행값을 받아와서 세번째 작업 실행
.then((message3) => {
    // 세번째 작업에서 받은 message3 출력
    console.log(message3);
})

.catch((error) => {  // 프로미스 체인중 발생한 에러 처리
    console.error(error);  // 에러 출력
});

 

* async/await

프로미스가 콜백헬을 해결할 수 있지만, 여전히 then과 catch가 반복되면서 코드가 장황할 수 있다.

이때 async/await 문법을 사용하면 프로미스 코드를 한 번 더 줄일 수 있다.

 

프로미스 

function findAndSaveUser(Users) {
    Users.findOne({})
    .then((user) => {
        user.name = 'zero';
        return user.save();
    })
    .then((user) => {
        return Users.findOne({ gender: 'm'});
    })
    .catch(err => {
        console.error(err);
    });
}

 

async/await

async function findAndSaveUser(Users) {   // function에서 async function으로 교체한 후 
    try{  // try/catch문으로 감싸서 에러 처리
        let user = await Users.findOne({});   // 프로미스 앞에 'await' 붙임
        user.name = 'zero';
        user = await user.save();
        user = await Users.findOne({gender: 'm'});
    } catch (error) {  // try/catch문으로 감싸서 에러 처리
        console.error(error);
    }
}

 

async/await 문법을 사용했더니 코드 길이가 짧아졌다.

 

 

728x90
반응형
LIST

댓글