본문 바로가기

Node.js/1. node.js 란?

1-4. 콜백 지옥

 

 

"콜백 지옥"이란, 비동기 함수에서 여러 개의 콜백을 중첩시키는 구조로 인해 코드가 들여쓰기(Indentation) 깊이가 많아지고 복잡해지는 현상을 의미합니다. Express 서버에서도 비동기 작업을 콜백으로 처리하다 보면 이런 현상이 발생할 수 있습니다.

아래는 콜백 지옥을 만든 간단한 Express 서버 예시입니다. 이 서버는 여러 비동기 작업을 콜백을 이용해 처리하는 방식으로 구성되어 있습니다.

예시 코드: 콜백 지옥

const express = require('express');
const app = express();

// 예시로 사용할 비동기 함수들 (콜백을 사용하는 비동기 함수들)
function step1(callback) {
    setTimeout(() => {
        console.log('Step 1 complete');
        callback(null, 'Step 1 Data');
    }, 1000);
}

function step2(data, callback) {
    setTimeout(() => {
        console.log('Step 2 complete with', data);
        callback(null, 'Step 2 Data');
    }, 1000);
}

function step3(data, callback) {
    setTimeout(() => {
        console.log('Step 3 complete with', data);
        callback(null, 'Step 3 Data');
    }, 1000);
}

// 라우터에서 콜백 지옥 예시
app.get('/callback-hell', (req, res) => {
    step1((err, result1) => {
        if (err) {
            res.status(500).send('Error in Step 1');
            return;
        }
        step2(result1, (err, result2) => {
            if (err) {
                res.status(500).send('Error in Step 2');
                return;
            }
            step3(result2, (err, result3) => {
                if (err) {
                    res.status(500).send('Error in Step 3');
                    return;
                }
                res.send('All steps completed: ' + result3);
            });
        });
    });
});


app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
});

 

위의 코드를 보면 각각의 실행 함수 그리고 해당 에러 처리를 위한 콜백이 연달아서 있는것을 볼수 있습니다.

에러 처리가 힘들뿐더러, 읽기에도 불편한 코드가 됩니다.

 

async/await를 사용하여 위의 "콜백 지옥" 코드를 개선할 수 있습니다. async/await는 비동기 코드를 동기적으로 작성하는 방식으로, 코드의 가독성을 높이고 중첩을 줄여줍니다. 아래는 async/await로 리팩토링한 코드입니다.

const express = require('express');
const app = express();

// 예시로 사용할 비동기 함수들 (콜백을 사용하는 비동기 함수들)
function step1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Step 1 complete');
            resolve('Step 1 Data');
        }, 1000);
    });
}

function step2(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Step 2 complete with', data);
            resolve('Step 2 Data');
        }, 1000);
    });
}

function step3(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Step 3 complete with', data);
            resolve('Step 3 Data');
        }, 1000);
    });
}

// /callback-hell 경로 처리 (async/await 사용)
app.get('/callback-hell', async (req, res) => {
    try {
        const result1 = await step1();
        const result2 = await step2(result1);
        const result3 = await step3(result2);
        res.send('All steps completed: ' + result3);
    } catch (err) {
        res.status(500).send('Error occurred: ' + err);
    }
});

// 서버 시작
app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
});

 

- 코드 가독성 향상: async/await를 사용하면 콜백을 중첩하지 않고 동기적인 흐름으로 비동기 작업을 처리할 수 있어 코드가 훨씬 읽기 쉬워집니다.

- 에러 처리: try/catch 구문으로 전체 비동기 코드에서 발생할 수 있는 오류를 한 곳에서 처리할 수 있습니다.

- 유지보수 용이: 코드가 간결해지고, 각 단계를 추적하기가 쉬워집니다.