Develope/Javascript

자바스크립트 비동기 처리: Callback, Promise, Async/Await 그리고 Yield

oper0116 2024. 8. 26. 16:49
반응형

자바스크립트는 싱글 스레드 기반 언어로, 한 번에 하나의 작업만 수행할 수 있다. 그러나 실제 웹 애플리케이션에서는 여러 작업을 동시에 처리해야 하는 경우가 많다. 예를 들어, 서버에서 데이터를 가져오거나, 파일을 읽거나, 사용자 입력을 기다리는 등의 작업이 있다.

이러한 비동기 작업을 처리하기 위해 자바스크립트는 다양한 방법을 제공한다. 이 글에서는 Callback, Promise, Async/Await에 이어 GeneratorYield 문법까지 살펴보려 한다.

Callback

Callback 함수란?

Callback 함수는 다른 함수에 인수로 전달되어 나중에 실행되는 함수이다. 비동기 작업이 완료된 후에 호출되는 함수로, 자바스크립트에서 비동기 처리를 구현하는 가장 기본적인 방법이다.

function fetchData(callback) {
    setTimeout(() => {
        callback("데이터가 준비되었습니다!");
    }, 2000);
}

fetchData(function (data) {
    console.log(data);
});

위 코드에서 fetchData 함수는 2초 후에 callback 함수를 호출하여 데이터를 반환한다. 콜백을 사용하면 비동기 작업이 완료된 후에 특정 작업을 수행할 수 있다.

콜백 헬(Callback Hell)

콜백의 단점 중 하나는 복잡한 비동기 로직을 처리할 때 발생하는 콜백 헬(Callback Hell)이다. 여러 비동기 작업이 중첩될 경우 코드의 가독성이 떨어지고, 유지보수가 어려워진다.

doSomething(function (result1) {
    doSomethingElse(result1, function (result2) {
        doAnotherThing(result2, function (result3) {
            doFinalThing(result3, function (result4) {
                console.log("모든 작업 완료");
            });
        });
    });
});

위와 같은 형태의 코드가 바로 Callback Hell의 예시이다.

Promise

Promise란?

Promise는 비동기 작업의 완료 또는 실패를 나타내는 자바스크립트 객체이며, Promise는 작업이 완료되었을 때의 결과 값을 처리하는 방법을 제공한다. 콜백과 달리, Promise는 더 체계적이고 가독성이 높은 코드를 작성할 수 있게 해준다.

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("데이터가 준비되었습니다!");
        }, 2000);
    });
}

fetchData()
    .then((data) => {
        console.log(data);
    })
    .catch((error) => {
        console.error(error);
    });

fetchData 함수는 Promise 객체를 반환하며, 비동기 작업이 성공하면 resolve, 실패하면 reject를 호출한다. .then() 메서드는 작업이 성공적으로 완료되었을 때 실행되며, .catch() 메서드는 작업이 실패했을 때 실행된다.

Promise Chaining

Promise의 또 다른 장점은 체이닝을 통해 비동기 작업을 순차적으로 처리할 수 있다는 것이다.

fetchData()
    .then((data) => {
        console.log(data);
        return processData(data);
    })
    .then((processedData) => {
        console.log(processedData);
        return saveData(processedData);
    })
    .then(() => {
        console.log("모든 작업 완료");
    })
    .catch((error) => {
        console.error(error);
    });

이렇게 하면 Callback Hell을 피하면서도 비동기 작업을 순차적으로 처리할 수 있다.

Async/Await

Async/Await란?

Async/Await는 ES2017에서 도입된 기능으로, Promise를 더 간결하게 사용하기 위한 문법이다.
Async/Await를 사용하면 비동기 코드를 마치 동기 코드처럼 읽기 쉽고 직관적으로 작성할 수 있다.

async function fetchDataAndProcess() {
    try {
        const data = await fetchData();
        console.log(data);
        const processedData = await processData(data);
        console.log(processedData);
        await saveData(processedData);
        console.log("모든 작업 완료");
    } catch (error) {
        console.error(error);
    }
}

fetchDataAndProcess();

async 키워드는 함수가 비동기임을 나타내며, await 키워드는 Promise가 해결될 때까지 기다린다. 이를 통해 콜백이나 .then() 체이닝 없이도 깔끔한 비동기 코드를 작성할 수 있다.

Async/Await의 장점

  • 가독성: 코드가 동기적으로 실행되는 것처럼 보이기 때문에 읽기 쉽다.
  • 에러 처리: try/catch 블록을 사용해 에러를 처리할 수 있다.
  • 코드 구조: 체이닝이 아닌 순차적인 코드 구조로 작성할 수 있다.

Generator와 Yield

Generator란?

Generator 함수는 비동기 처리를 위한 특별한 함수로, 함수의 실행을 중간에 멈추고 재개할 수 있는 기능을 제공한다. function* 키워드를 사용하여 정의하며, 함수 실행을 일시 중지하고 값을 반환하기 위해 yield 키워드를 사용한다.

Yield란?

yield는 Generator 함수 내에서 사용되어 함수의 실행을 일시 중지하고, next() 메서드를 호출할 때까지 대기한다. next()를 호출하면 yield 뒤의 표현식이 실행되고, 함수가 재개된다.

function* fetchData() {
    console.log("첫 번째 요청");
    yield "첫 번째 응답";

    console.log("두 번째 요청");
    yield "두 번째 응답";

    console.log("세 번째 요청");
    return "모든 요청 완료";
}

const generator = fetchData();

console.log(generator.next().value); // 첫 번째 요청 후 첫 번째 응답 반환
console.log(generator.next().value); // 두 번째 요청 후 두 번째 응답 반환
console.log(generator.next().value); // 세 번째 요청 후 모든 요청 완료 반환

비동기 작업에서의 Generator 사용

Generator는 비동기 작업에서도 사용할 수 있다. 특히 yield를 Promise와 함께 사용하면 복잡한 비동기 로직을 제어하는 데 유용하다.

function* fetchDataAndProcess() {
    const data = yield fetchData();
    console.log(data);

    const processedData = yield processData(data);
    console.log(processedData);

    yield saveData(processedData);
    console.log("모든 작업 완료");
}

function handleGenerator(gen) {
    const generator = gen();

    function step(value) {
        const result = generator.next(value);
        if (!result.done) {
            result.value.then(step).catch((err) => generator.throw(err));
        }
    }

    step();
}

handleGenerator(fetchDataAndProcess);

위 코드에서 handleGenerator 함수는 Generator를 실행하며, yield된 Promise가 해결될 때마다 Generator를 재개한다. 이렇게 하면 비동기 작업을 제어하는 데 매우 강력한 도구가 될 수 있다.

결론

자바스크립트에서 비동기 처리를 위해 다양한 패턴을 사용할 수 있다

  • Callback: 가장 기본적인 비동기 처리 방법이지만, 복잡한 로직에서는 콜백 헬을 초래할 수 있다
  • Promise: 콜백의 단점을 보완하며, 체이닝을 통해 가독성 있는 비동기 코드를 작성할 수 있다.
  • Async/Await: Promise를 간결하고 동기적인 코드처럼 작성할 수 있어 가독성이 높다.
  • GeneratorYield: 함수의 실행을 중지하고 재개할 수 있는 기능을 제공하여, 복잡한 비동기 로직을 제어하는 데 유용하다.

각 패턴의 장단점을 이해하고 상황에 맞게 선택하여 사용하면, 자바스크립트에서 비동기 작업을 더욱 효율적으로 처리할 수 있다.

반응형