비동기 JavaScript 초보자를 위한 가이드

javascript
약속
AsyncAwait
비동기 JavaScript 초보자를 위한 가이드 cover image

프로그래밍을 시작하는 경우라면 프로그램을 일련의 논리 블록으로 생각할 가능성이 높습니다. 여기서 각 블록은 특정 작업을 수행하고 그 결과를 전달하여 다음 블록이 실행될 수 있도록 합니다. 대부분 당신 말이 맞습니다. 대부분의 프로그램은 순차적 방식으로 실행됩니다. 이 모델을 사용하면 작성하고 유지 관리하기 쉬운 프로그램을 만들 수 있습니다. 그러나 이 순차 모델이 작동하지 않거나 최적이 아닌 특정 사용 사례가 있습니다. 예를 들어 책 리더 애플리케이션을 생각해 보세요. 이 애플리케이션에는 모든 단어 찾기, 북마크 간 탐색 등과 같은 몇 가지 고급 기능이 있습니다. 이제 사용자가 현재 긴 책을 읽고 있고 "The"와 같은 일반적인 단어가 나오는 것을 모두 찾기로 결정했다고 상상해 보십시오. 응용프로그램은 일반적으로 해당 단어의 모든 항목을 찾아 색인화하는 데 몇 초 정도 걸립니다. 순차 프로그램에서 사용자는 검색 작업이 완료될 때까지 애플리케이션과 상호 작용(페이지 변경 또는 텍스트 강조 표시)할 수 없습니다. 바라건대, 이것이 최적의 사용자 경험이 아니라는 것을 알 수 있을 것입니다!

1

다이어그램은 책 리더 애플리케이션의 일반적인 실행 흐름을 보여줍니다. 사용자가 장기 실행 작업(이 경우 큰 책에서 "the"가 나오는 모든 항목 검색)을 시작하면 해당 작업이 진행되는 동안 응용 프로그램이 "정지"됩니다. 이 경우 사용자는 검색 작업이 완료될 때까지 결과 없이 다음 북마크 버튼을 계속 클릭하게 되며 모든 작업이 한 번에 적용되어 최종 사용자에게 지연된 애플리케이션의 느낌을 줍니다.

이 예는 앞서 소개한 순차 모델과 실제로 일치하지 않는다는 점을 눈치챘을 것입니다. 이는 여기서의 작업이 서로 독립적이기 때문입니다. 사용자는 다음 북마크로 이동하기 위해 "the"의 발생 횟수를 알 필요가 없으므로 작업 실행 순서는 그다지 중요하지 않습니다. 다음 북마크로 이동하기 전에 검색 작업이 끝날 때까지 기다릴 필요가 없습니다. 이전 실행 흐름에 대한 가능한 개선은 이 논리를 기반으로 합니다. 즉, 백그라운드에서 긴 검색 작업을 실행하고 들어오는 작업을 진행할 수 있으며 긴 작업이 완료되면 사용자에게 간단히 알릴 수 있습니다. 실행 흐름은 다음과 같습니다.

2

이 실행 흐름을 통해 사용자 경험이 크게 향상됩니다. 이제 사용자는 장기 실행 작업을 시작하고 정상적으로 애플리케이션을 사용할 수 있으며 작업이 완료되면 알림을 받을 수 있습니다. 이것이 비동기 프로그래밍의 기초입니다.

다른 언어 중에서도 Javascript는 생각할 수 있는 거의 모든 비동기 동작을 달성할 수 있는 광범위한 API를 제공하여 이러한 스타일의 비동기 프로그래밍을 지원합니다. 결국 Javascript는 본질적으로 비동기 언어여야 합니다. 이전 예를 참조하면 비동기 논리는 모든 사용자 상호 작용 애플리케이션의 기본이며 Javascript는 주로 대부분의 프로그램이 사용자 작업에 응답하는 브라우저에서 사용되도록 구축되었습니다.

다음은 비동기 Javascript에 대한 간략한 가이드를 제공합니다.

콜백

일반적인 프로그램에서는 일반적으로 여러 가지 기능을 찾을 수 있습니다. 함수를 사용하려면 매개변수 세트를 사용하여 함수를 호출합니다. 함수 코드는 실행되고 결과를 반환하며, 일반적인 결과는 없습니다. 비동기 프로그래밍은 이 논리를 약간 전환합니다. 책 리더 애플리케이션의 예로 돌아가면, 작업에 알 수 없는 시간이 걸리기 때문에 일반 함수를 사용하여 검색 작업 로직을 구현할 수 없습니다. 일반 함수는 기본적으로 작업이 완료되기 전에 반환되며 이는 우리가 기대하는 동작이 아닙니다. 해결책은 검색 작업이 완료되면 실행될 다른 기능을 지정하는 것입니다. 이는 프로그램이 정상적으로 흐름을 계속할 수 있고 검색 작업이 완료되면 지정된 함수가 실행되어 사용자에게 검색 결과를 알리기 때문에 사용 사례를 모델링합니다. 이 함수를 콜백 함수라고 부릅니다.

// Search occurrences function
function searchOccurrences(word, callback) {
  try {
    // search operation logic, result is in result variable
    //....
    callback(null, word, result);
  } catch (err) {
    callback(err);
  }
}

// Search occurrences callback function
function handleSearchOccurrencesResult(err, word, result) {
  if (err) {
    console.log(`Search operation for ${word} ended with an error`);
  } else console.log(`Search results for ${word}: ${result}`);
  return;
}

searchOccurrences("the", handleSearchOccurrencesResult);

먼저 검색 연산 함수인 searchOccurrences를 정의합니다. 검색할 단어와 검색 작업이 완료되면 실행할 함수가 될 두 번째 매개변수인 "콜백"이 필요합니다. 검색 작업 기능은 의도적으로 추상적으로 유지되었으므로 가능한 두 가지 결과에만 집중하면 됩니다. 첫 번째 경우는 모든 것이 성공한 경우이고 결과 변수에 검색 결과가 있습니다. 이 경우 다음 매개변수를 사용하여 콜백 함수를 호출하면 됩니다. 첫 번째 매개변수는 null이며 오류가 발생하지 않았음을 의미하고, 두 번째 매개변수는 검색된 단어이며, 세 번째 매개변수이자 아마도 가장 중요한 매개변수일 것입니다., 검색 작업의 결과입니다.

두 번째 경우는 오류가 발생하는 경우인데, 이 경우도 검색 작업이 수행되어 콜백 함수를 호출해야 하는 경우입니다. 우리는 모든 오류를 가로채기 위해 try 및 catch 블록을 사용하고 catch 블록의 오류 개체를 사용하여 콜백 함수를 호출합니다.

그런 다음 콜백 함수인 handlerSearchOccurrences를 정의하고 해당 논리를 매우 단순하게 유지했습니다. 콘솔에 메시지를 출력하기만 하면 됩니다. 먼저 “err” 매개변수를 확인하여 주 함수에 오류가 발생했는지 확인합니다. 이 경우 검색 작업이 오류로 종료되었음을 사용자에게 알립니다. 오류가 발생하지 않으면 검색 작업 결과와 함께 메시지를 인쇄합니다.

마지막으로 "the"라는 단어를 사용하여 searchOccurrences 함수를 호출합니다. 이제 함수는 기본 프로그램을 차단하지 않고 정상적으로 실행되며 검색이 완료되면 콜백이 실행되고 검색 결과 또는 오류 메시지와 함께 결과 메시지가 표시됩니다.

여기서는 기본 함수와 콜백 함수 내부의 결과 변수에만 액세스할 수 있다는 점을 언급하는 것이 중요합니다. 다음과 같이 시도해 보면:

let result;
function searchOccurrences(word, callback) {
  try {
    // search operation logic, result is in searchResult variable
    //....
    result = searchResult;
    callback(null, word, result);
  } catch (err) {
    callback(err);
  }
}
searchOccurrences("the", handleSearchOccurrencesResult);
console.log(result);

프로그램은 searchOccurrences 함수가 실행될 때까지 기다리지 않기 때문에 인쇄 결과는 정의되지 않습니다. 결과 변수가 주 함수 내부에 할당되기 전에 인쇄 문인 다음 명령어로 이동합니다. 결과적으로 할당되지 않은 결과 변수를 인쇄합니다.

따라서 이 논리에 따라 결과 변수를 사용하는 모든 코드를 콜백 함수 내부에 유지해야 합니다. 지금은 문제가 아닌 것처럼 보일 수도 있지만 실제 문제로 빠르게 확대될 수 있습니다. 순차적으로 실행해야 하는 일련의 비동기 함수가 있는 경우를 상상해 보십시오. 일반적인 콜백 논리에서는 다음과 같이 구현합니다.

functionA(function (err, resA) {
  ///......
  functionB(resA, function (err, resB) {
    ///......
    functionC(resB, function (err, resC) {
      ///......
      functionD(resC, function (err, resD) {
        ///......
      });
    });
  });
});

각 콜백에는 오류 매개변수가 있으며 각 오류는 별도로 처리되어야 한다는 점을 명심하세요. 이는 위의 이미 복잡한 코드를 더욱 복잡하고 유지 관리하기 어렵게 만듭니다. 콜백 지옥 문제를 해결하기 위한 Promise가 있기를 바랍니다. 이에 대해서는 다음에 다루겠습니다.

약속

Promise는 콜백을 기반으로 구축되며 비슷한 방식으로 작동합니다. 콜백 지옥과 같은 콜백과 관련된 몇 가지 눈에 띄는 문제를 해결하기 위해 ES6 기능의 일부로 도입되었습니다. Promise는 성공적인 완료(해결) 및 오류 발생(거부) 시 실행되는 자체 기능을 제공합니다. 다음은 약속으로 구현된 searchOccurrences 예제를 보여줍니다.

// Search occurrences function
function searchOccurrences(word) {
  return new Promise((resolve, reject) => {
    try {
      // search operation logic, result is in result variable
      //....
      resolve(word, result);
    } catch (err) {
      reject(err);
    }
  });
}

searchOccurrences("the")
  .then((word, result) => {
    console.log(`Search results for ${word}: ${result}`);
  })
  .catch((err) => {
    console.log(`Search operation ended with an error`);
  });

우리가 적용한 변경 사항을 살펴보겠습니다.

searchOccurrences 함수는 약속을 반환합니다. Promise 내부에는 동일한 논리가 유지됩니다. 성공적인 실행과 오류가 있는 실행을 모두 처리하는 단일 콜백 함수가 아닌 콜백을 나타내는 두 개의 함수인 해결 및 거부가 있습니다. Promise는 두 결과를 분리하고 메인 함수를 호출할 때 깔끔한 구문을 제공합니다. 해결 기능은 "then" 키워드를 사용하여 기본 기능에 "연결"됩니다. 여기서는 해결 함수의 두 매개변수를 지정하고 검색 결과를 인쇄합니다. 거부 기능에도 비슷한 내용이 적용됩니다. "catch" 키워드를 사용하여 연결할 수 있습니다. 코드 가독성과 청결성 측면에서 Promise가 제공하는 이점을 이해하시기 바랍니다. 아직도 논쟁 중이라면 비동기 함수를 서로 연결하여 하나씩 실행하여 콜백 지옥 문제를 해결할 수 있는 방법을 확인하세요.

searchOccurrences("the")
  .then(searchOccurrences("asynchronous"))
  .then(searchOccurrences("javascript"))
  .then(searchOccurrences("guide"))
  .catch((err) => {
    console.log(`Search operation ended with an error`);
  });

비동기/대기

Async/Await는 Javascript의 비동기 툴벨트에 최근 추가된 기능입니다. ES8에서 도입된 이 기능은 단순히 비동기 작업 실행을 "기다림"으로써 비동기 기능 위에 새로운 추상화 계층을 제공합니다. 비동기 작업에서 결과가 반환될 때까지 해당 명령어에서 프로그램 흐름이 차단되고 그 후 프로그램은 다음 명령어로 진행됩니다. 동기 실행 흐름에 대해 생각하고 있다면 맞습니다. 우리는 완전한 원으로 왔습니다! Async/await는 동기 프로그래밍의 단순성을 비동기 세계에 가져오려고 시도합니다. 이는 프로그램의 실행과 코드에서만 인식된다는 점을 명심하세요. 내부적으로는 모든 것이 동일하게 유지되며 Async/await는 여전히 Promise를 사용하고 콜백은 구성 요소입니다.

예제를 살펴보고 Async/await를 사용하여 구현해 보겠습니다.

async function searchOccurrences(word) {
  try {
    // search operation logic, result is in result variable
    //....
    return result;
  } catch (err) {
    console.log(`Search operation for ${word} ended with an error`);
  }
}

const word = "the";

const result = await searchOccurrences(word, handleSearchOccurrencesResult);

console.log(`Search results for ${word}: ${result}`);

코드는 많이 변경되지 않았습니다. 여기서 주목해야 할 중요한 점은 searchOccurrences 함수 선언 앞에 있는 "async" 키워드입니다. 이는 함수가 비동기적임을 나타냅니다. 또한 searchOccurrences 함수를 호출할 때 "await" 키워드를 확인하세요. 이는 프로그램이 다음 명령어로 이동하기 전에 결과가 반환될 때까지 함수 실행을 기다리도록 프로그램에 지시합니다. 즉, 결과 변수는 항상 searchOccurrences 함수의 반환된 값을 보유하며 다음의 약속은 유지하지 않습니다. 그런 의미에서 Async/Await 함수에는 Promises라는 보류 상태가 없습니다. 실행이 완료되면 print 문으로 이동하고 이번에는 결과에 실제로 검색 작업의 결과가 포함됩니다. 예상한 대로 새 코드는 동기 코드와 동일한 동작을 갖습니다.

명심해야 할 또 다른 사소한 점은 더 이상 콜백 함수가 없기 때문에 오류를 콜백 함수에 전파하고 거기서 처리할 수 없기 때문에 동일한 함수 내에서 searchOccurrences 오류를 처리해야 한다는 것입니다. 여기서는 예제를 위해 오류가 발생할 경우 오류 메시지를 인쇄합니다.

마무리

이 기사에서는 Javascript에서 비동기 논리를 구현하는 데 사용되는 다양한 접근 방식을 살펴보았습니다. 우리는 일반적인 동기 프로그래밍 스타일에서 비동기 모델로 전환해야 하는 이유에 대한 구체적인 예를 탐색하는 것부터 시작했습니다. 그런 다음 비동기 Javascript의 주요 구성 요소인 콜백으로 이동했습니다. 콜백의 한계로 인해 이러한 한계를 극복하기 위해 수년에 걸쳐 추가된 다양한 대안(주로 promise 및 Async/await)이 탄생했습니다. 외부 API 호출, 데이터베이스 쿼리 시작, 로컬 파일 시스템에 쓰기, 로그인 양식에서 사용자 입력 대기 등 웹 어디에서나 비동기 논리를 찾을 수 있습니다. 이제 깨끗하고 유지 관리가 가능한 비동기 Javascript를 작성하여 이러한 문제를 더 자신감있게 해결할 수 있기를 바랍니다!

이 기사가 마음에 드셨다면 기술 분야에 진출하는 방법에 대한 다양한 주제를 논의하는 CLA 블로그를 확인해 보세요. 또한 이전 무료 워크숍을 보려면 youtube 채널을 확인하고 소셜 미디어에서 우리를 팔로우하세요. -labs-academy/) 다가오는 과정을 놓치지 마세요!


Code Labs Academy의 웹 개발 부트캠프를 통해 HTML, CSS, JavaScript 기술을 향상하여 미래에 경력을 쌓을 수 있습니다.


Career Services background pattern

취업 서비스

Contact Section background image

계속 연락하자

Code Labs Academy © 2024 판권 소유.