동기 vs 비동기 프로그래밍: 개념부터 실제 적용까지

프로그래밍 세계에서 '동기(Synchronous)''비동기(Asynchronous)'는 데이터 처리 방식을 규정하는 핵심 개념입니다. 이 두 방식은 코드 실행의 순서와 타이밍에 큰 차이를 두며, 각각의 방식은 고유의 장단점을 가지고 있습니다. 이 글에서는 이러한 개념들을 명확히 하고, JavaScript에서 비동기 프로그래밍을 구현하는 다양한 방법을 살펴보겠습니다.

JS에서의 동기와 비동기 프로그래밍
JS에서의 동기와 비동기 프로그래밍

동기 프로그래밍: 순차적 실행의 명확성

동기 프로그래밍에서는 코드가 순차적으로 실행됩니다. 즉, 한 작업이 완료될 때까지 다음 작업은 시작되지 않습니다. 이는 코드의 실행 흐름을 예측하기 쉽게 만들며, 디버깅을 간소화하는 장점이 있습니다. 예를 들어, 다음 코드는 '1', '2', '3'을 순서대로 콘솔에 출력합니다.

console.log("1");
console.log("2");
console.log("3");

이 방식은 간단하고 직관적이지만, 특정 작업이 시간이 많이 소요될 경우 애플리케이션의 전반적인 성능에 영향을 미칠 수 있습니다.

 

비동기 프로그래밍: 효율성과 병렬 처리

비동기 프로그래밍은 코드가 병렬로 실행될 수 있게 합니다. 이는 한 작업의 완료를 기다리지 않고 즉시 다음 작업을 시작할 수 있음을 의미합니다. JavaScript에서는 주로 콜백 함수, Promise, async/await 등의 방법으로 비동기 작업을 처리합니다.

1. 콜백 함수: 기본적인 비동기 처리

콜백 함수는 비동기 작업이 완료된 후 실행되어야 할 함수를 말합니다. 비동기 함수 내에서 콜백 함수를 실행시켜, 작업의 완료 후 추가 작업을 진행할 수 있습니다.

function downloadFile(url, callback) {
    setTimeout(() => { // 가정된 비동기 작업
        console.log('Download complete.');
        callback(null, "File data");
    }, 3000);
}

downloadFile('http://example.com/file.txt', function(error, data) {
    if (error) {
        console.error('Error:', error);
        return;
    }
    console.log('File processed:', data);
});

콜백 함수는 단순하고 이해하기 쉬운 반면, 여러 비동기 작업이 연속적으로 발생할 경우 코드의 가독성이 떨어지는 '콜백 지옥'으로 이어질 수 있습니다.

2. Promise 객체: 체이닝과 에러 핸들링의 용이성

Promise는 비동기 작업의 최종 성공 또는 실패를 나타내는 객체입니다. then 메서드를 통해 성공 시의 동작을, catch 메서드를 통해 실패 시의 동작을 정의할 수 있습니다.

function myAsyncFunction(flag) {
    return new Promise((resolve, reject) => {
        if (flag) {
            resolve("Operation successful.");
        } else {
            reject(new Error("Operation failed."));
        }
    });
}

myAsyncFunction(true)
    .then(message => console.log(message))
    .catch(error => console.error(error));

myAsyncFunction(false)
    .then(message => console.log(message))
    .catch(error => console.error(error));

Promise는 비동기 작업을 순차적으로 연결할 수 있게 하며, 여러 단계의 오류를 한 곳에서 처리할 수 있는 장점을 제공합니다.

3. async/await: 동기적 코드의 가독성

async/await는 비동기 코드를 동기적으로 보이게 하는 문법으로, 가독성을 크게 향상시킵니다. async 함수는 항상 Promise를 반환하며, await는 Promise의 해결을 기다립니다.

async function processData() {
    try {
        const data = await downloadData();
        console.log('Data processed:', data);
    } catch (error) {
        console.error('Processing error:', error);
    }
}

processData();

async/await는 코드의 가독성을 높이고, 오류 처리를 간결하게 만듭니다. 단, 병렬 처리에는 Promise.all() 등의 방법이 더 적합합니다.

 

비동기 프로그래밍의 병렬 처리: Promise.all()과 Promise.race()

Promise.all()은 여러 Promise를 병렬로 처리하고, 모든 Promise가 해결될 때 결과를 반환합니다. 반면, Promise.race()는 주어진 Promise 중 가장 먼저 해결되거나 거부된 Promise의 결과를 반환합니다.

Promise.all([asyncTask1(), asyncTask2()])
    .then(results => {
        const [result1, result2] = results;
        console.log('Task 1 result:', result1);
        console.log('Task 2 result:', result2);
    })
    .catch(error => console.error('Error in tasks:', error));

이러한 방법은 여러 비동기 작업을 효율적으로 관리하고, 작업의 동시 실행을 최적화하는 데 유용합니다.

 

비동기 프로그래밍은 JavaScript에서 강력한 도구이며, 적절한 방법을 선택하여 사용하면 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있습니다. 각 방법의 장단점을 이해하고 상황에 맞게 적용하는 것이 중요합니다.