<p style="text-align:justify;">자바스크립트를 사용하다 보면 ‘비동기’라는 개념을 자주 접하게 됩니다. 특히 API를 호출하거나, 시간이 걸리는 작업을 처리할 때 비동기는 꼭 필요한 기능이죠. 하지만 처음 접하면 이해하기 어려운 부분이 많아, “이거 대체 왜 이렇게 동작하지?”라는 의문이 들기도 합니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">비동기 처리 개념을 쉽게 이해하려면, 동기(Synchronous)와 비동기(Asynchronous)의 차이를 먼저 알아야 합니다. 그리고 자바스크립트에서 비동기를 처리하는 여러 방법, setTimeout, Promise, async/await 등에 대해 살펴보면, 왜 비동기가 필요한지 그리고 비동기를 어떻게 활용해야 하는지 감이 오기 시작할 겁니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">이번 글에서는 비동기의 기초부터 시작해, 비동기 처리의 핵심 개념과 기본적인 구현 방법에 대해 살펴보겠습니다.</p><div class="page-break" style="page-break-after:always;"><span style="display:none;"> </span></div><h3 style="text-align:justify;"><strong>꼭 비동기를 사용해야 할까?</strong></h3><p style="text-align:justify;">자바스크립트에서 비동기를 사용해야 하는 이유는 무엇일까요? 이를 이해하기 위해 먼저 동기와 비동기의 차이를 살펴보고, 자바스크립트가 왜 비동기 처리를 필요로하는지 알아보겠습니다. 먼저, 동기 방식으로 실행되는 코드를 살펴보겠습니다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">console.log("작업 1 시작"); console.log("작업 2 시작"); console.log("작업 3 시작");</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">위와 같은 코드를 실행하면 우리 모두가 알고 있는 것처럼, 코드가 위에서 아래로, 순서대로 실행되기 때문에 “작업 1 시작”, “작업 2 시작”, “작업 3 시작”이 출력됩니다. 이렇게 코드가 위에서 아래로 차례대로 실행되는 방식이 바로 “동기 처리”입니다. </p><p style="text-align:justify;"> </p><p style="text-align:justify;">하지만 만약 시간이 오래 걸리는 작업(서버에서 데이터 가져오기, 파일 읽기 등)이 있다면 어떻게 될까요? 아래 코드를 통해 살펴보겠습니다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">console.log("작업 1 시작"); sleep(5000); // 5초 동안 멈추는 함수(예시) console.log("작업 2 시작"); console.log("작업 3 시작");</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">위의 코드가 실행되면, sleep(5000) 함수가 실행되는 5초 동안은 아무런 작업도 할 수 없겠죠. 즉, 작업 2와 작업 3이 실행되지 않고, 5초 동안 프로그램이 멈추게 됩니다. 이렇게 동기 처리의 단점은 한 작업이 끝나야만, 다음 작업이 실행되기 때문에 실행 시간이 오래 걸리는 작업이 있으면 전체적인 코드의 실행이 지연된다는 점입니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">그렇다면 비동기 방식으로 실행하면 어떻게 달라질까요?</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">console.log("작업 1 시작"); setTimeout(() => { console.log("작업 2 시작"); }, 5000); // 5초 후 실행 console.log("작업 3 시작");</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">위의 코드를 실행하면, “작업 1 시작”, “작업 3 시작”, 그리고 5초 후에 “작업 2 시작”이 출력됩니다. 비동기 처리는 setTimeout과 같은 비동기 함수를 사용해 긴 작업을 기다리는 동안 다른 작업을 먼저 실행할 수 있도록 도와줍니다. 즉, setTimeout 함수가 실행된 후 5초를 기다리는 동안, 다음 코드인 “작업 3 시작”이 먼저 실행되는 것이죠.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">이를 바탕으로 비동기와 동기 방식의 차이를 그림으로 나타내 보면, 다음과 같습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img1.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">이처럼 비동기 처리를 사용하면 시간이 오래 걸리는 작업이 있어도, 전체 코드 실행이 중간에 멈추지 않고 원활하게 진행될 수 있습니다. 그럼 이제 자바스크립트가 왜 기본적으로 동기 방식이 아니라, 비동기 방식을 필요로 하는지 더 깊이 배워보겠습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>1) 싱글 스레드와 멀티 스레드</strong></h4><p style="text-align:justify;">자바스크립트는 기본적으로 싱글 스레드(Single Thread)를 기반으로 동작하는 언어입니다. 스레드는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 의미하기 때문에, 싱글 스레드란 하나의 스레드에서 작업을 처리하는 방식을 말합니다. 즉, 하나의 코드가 실행되는 동안 다른 코드는 기다려야 하는 것이죠.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">다시 말해 싱글 스레드는 CPU가 한 번에 하나의 작업만 수행할 수 있는 방식으로, 그림으로 나타내면 다음과 같습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img2.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">반면, 멀티 스레드(Multi Thread) 환경에서는 여러 개의 작업을 동시에 실행할 수 있습니다. 멀티 스레드 방식은 여러 작업을 병렬적으로 실행할 수 있기 때문에, 대기 시간이 줄어들고 프로그램이 더욱 효율적으로 동작할 수 있습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img3.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">이렇게 싱글 스레드와 멀티 스레드를 비교해서 보면, 멀티 스레드 방식이 싱글 스레드보다 효율적인 것처럼 보이는데, 자바스크립트는 왜 기본적으로 싱글 스레드일까요?</p><p style="text-align:justify;"> </p><p style="text-align:justify;">웹 브라우저에서 실행되는 자바스크립트는 주로 UI를 조작하고 이벤트를 처리하는 역할을 하기 때문에, 여러 개의 스레드가 동시에 DOM을 변경하면 충돌이 발생할 가능성이 큽니다. 따라서 이를 방지하기 위해 자바스크립트는 싱글 스레드 방식을 유지하면서도 비동기 처리를 통해 멀티 태스킹처럼 동작할 수 있도록 설계되었습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>2) 자바스크립트에서 작업을 비동기로 처리해야 하는 이유</strong></h4><p style="text-align:justify;">동기 방식에서는 코드가 위에서 아래로 순차적으로 실행되지만, 실행 시간이 오래 걸리는 작업(서버에서 데이터 가져오기, 파일 읽기 등)이 있다면, 전체적인 코드 실행이 지연됩니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img4.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">코드의 실행 순서가 위와 같다고 가정해 볼게요. 동기적으로 실행될 때는, 가장 오래 걸리는 B 작업이 실행 완료될 때까지, 그 이후의 작업들이 실행되지 않고 중단됩니다. </p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img5.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">즉, 위의 그림과 같이 이미지 정보를 받아오기 전까지는 화면에 텍스트와 같은 요소들이 표시되지 않고, 상호작용 또한 이루어지지 않습니다. 이러한 상황이 발생하면, 사용자들은 웹 사이트에 오류가 발생했다고 느끼겠죠.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img6.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">반면, 위의 그림처럼 이를 비동기적으로 처리한다면 각각의 작업들이 완료되는 것을 기다리지 않고 작업이 처리되는 순서대로 요소들이 화면에 나타납니다. 따라서 시간이 오래 걸리는 작업이 있더라도 전체적인 코드의 실행이 중단되지 않고 원활하게 진행될 수 있습니다. 화면에 로딩 화면이 나타난 2초 후에 텍스트가 먼저 보이고, 그다음 가장 오래 걸리는 이미지가 4초 후에 나타나죠. 그럼 이제 본격적으로 작업을 비동기로 처리하는 코드에 대해 배워볼게요.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>setTimeout</strong></h3><p style="text-align:justify;">자바스크립트에서 비동기 처리를 이해하는 가장 기본적인 방법 중 하나는 바로 setTimeout 함수를 사용하는 것입니다. 이 함수는 일정 시간이 지난 후에 특정 코드를 실행할 수 있도록 도와주는데요, 비동기 실행을 이해할 수 있는 가장 쉬운 방법이기 때문에 비동기 프로그래밍을 처음 접하는 사람들에게 유용한 개념입니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>1) 메서드 살펴보기</strong></h4><p style="text-align:justify;">setTimeout을 활용하면, 코드의 실행을 특정 시간 동안 지연할 수 있습니다. 즉, setTimeout을 사용하면 현재 실행 중인 코드의 흐름을 방해하지 않으면서도, 일정 시간이 지난 후 특정 로직을 실행할 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">setTimeout 함수의 기본적인 문법은 다음과 같습니다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">setTimeout(callback, delayTime);</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">여기서 callback은 지연 시간이 끝난 이후 실행될 함수를, delayTime은 밀리초 단위의 지연 시간을 의미합니다. 이제 본격적으로 setTimeout 함수를 사용해서 작업을 비동기로 처리해 보겠습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>2) 사용 예시</strong></h4><p style="text-align:justify;">3초 후에 “3초 후 실행”이라는 메시지를 출력하는 코드를 함께 작성해 볼게요.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">console.log("시작"); setTimeout(() => { console.log("3초 후 실행"); }, 3000); console.log("종료");</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">위의 코드를 보면, setTimeout 함수의 callback 함수로는 console.log(“3초 후 실행”);을, delayTime으로는 3,000ms를 전달했기 때문에, 제일 먼저 “시작”이라는 메시지가 출력되고 그다음으로는 “종료” 메시지가 출력되며, 마지막으로 코드 실행 3초 후에 “3초 후 실행”이라는 메시지가 출력됩니다. 이처럼 setTimeout 함수를 활용하면 코드 실행 흐름을 지연시키지 않으면서도, 비동기적으로 특정 시점에 원하는 작업을 수행할 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>프로미스 객체 간단하게 살펴보기</strong></h3><p style="text-align:justify;">setTimeout과 같은 콜백 기반의 비동기 처리는 간단한 작업에서는 유용하지만, 콜백 지옥(Callback Hell) 문제를 일으킬 수 있습니다. 자바스크립트에는 이러한 문제를 해결하면서, 조금 더 구조적인 방식으로 비동기 코드를 관리할 수 있는 프로미스(Promise)라는 객체가 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">비동기는 이전 작업의 처리가 완료될 때까지 기다리지 않고, 기본적으로는 다음 작업을 병렬적으로 처리하는 방식이기 때문에, 비동기 처리 작업은 항상 작업 처리의 성공 여부에 따라 함수를 다르게 호출해야 한다는 특징이 있습니다. </p><p style="text-align:justify;"> </p><p style="text-align:justify;">이때 프로미스 객체는 비동기 작업의 성공 또는 실패를 나눠서 처리할 수 있게 해주고, 비동기 작업이 성공적으로 완료된 경우에는 결괏값을 반환하고, 실패하면 오류를 처리할 수 있도록 도와줍니다. 이번 글에서는 프로미스 객체에 대한 자세한 내용보다는 어떻게 생성하고 사용하는지, 그리고 프로미스 객체의 동작 방식에 대해서만 간단하게 살펴보겠습니다. </p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>1) 생성 및 사용법</strong></h4><p style="text-align:justify;">프로미스 객체는 new 키워드와 생성자를 사용해서 생성할 수 있습니다. 프로미스 객체는 객체 생성 시, 인수로 executor라는 실행 함수를 전달하고, 실행 함수에는 매개변수로 resolve와 reject라는 콜백 함수를 전달합니다. 코드로 살펴봅시다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">const executor = (resolve, reject) => { //코드 }; const promise = new Promise(executor); console.log(promise); // Promise{<pending>}</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">위의 코드에서 executor 실행 함수는 프로미스 객체의 생성자에 반드시 전달해야 하는 함수로, 프로미스 객체가 생성될 때 자동으로 실행되는 함수입니다. 프로미스 객체가 생성됨과 동시에 executor 함수가 실행되고, executor에서 원하는 작업이 처리됩니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">비동기는 이전 작업의 처리가 완료될 때까지 기다리지 않고, 다음 작업을 병렬적으로 처리하는 방식이기 때문에 비동기 처리 작업은 항상 작업 처리의 성공 여부에 따라 함수를 다르게 호출해야한다는 특징이 있는데요. 조금 더 자세히 살펴볼게요.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>2) resolve와 reject</strong></h4><p style="text-align:justify;">프로미스 객체의 executor는 작업 처리의 성공 여부에 따라 성공했을 땐 resolve를, 실패했을 땐 reject를 호출합니다. 생성자를 통해 생성된 프로미스 객체는 state와 result라는 프로퍼티를 갖습니다. resolve와 reject 함수는 어떻게 동작하는 함수인지 살펴보고, 작업 처리의 성공 여부에 따라 state와 result 프로퍼티 값이 어떻게 변경되는지 알아보겠습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://www.wishket.com/media/news/3034/20250203-img7.png"><figcaption><출처: 작가></figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">프로미스 객체가 생성되면, 바로 executor 함수가 실행되고, 이때 state 값은 pending, result 값은 undefined가 할당됩니다. 이후 만약 특정 작업이 성공해서 resolve 함수가 호출되면, state는 fulfilled의 값을 갖고, result는 resolve 함수에 전달된 value의 값을 갖습니다. </p><p style="text-align:justify;"> </p><p style="text-align:justify;">반대로 특정 작업이 실패해서 reject 함수가 호출된다면 state는 rejected, 즉, 실패를 나타내는 값이 그리고 result에는 error가 전달됩니다. 실제로 3초 후에 “성공”을 전달하는 비동기 함수를 resolve를 사용해 작성해 보고, 오류가 발생했을 때는 “실패”를 전달하는 코드를 reject를 사용해서 작성해 봅시다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">const executor = (resolve, reject) => { setTimeout(() => { resolve('성공'); reject('실패'); }, 3000); }; const promise = new Promise(executor);</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">특정 작업을 3초 뒤에 수행해야 하므로, 실행 함수 executor 내부에는 setTimeout 메서드를 작성하고, delayTime으로 3,000ms를 전달했습니다. 그리고 setTimeout 함수의 내부에 resolve 함수를 사용해서 “성공”을 출력하고, reject 함수를 사용해서 “실패”를 출력한 다음, 프로미스 생성자 함수를 사용해서 프로미스 객체를 생성하고, promise 변수에 할당해 주었습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">그럼 이번에는 프로미스 객체를 사용한 비동기 작업의 결과를 출력해 볼까요? resolve와 reject 함수에 전달된 값을 출력하는 코드는 다음과 같이 작성할 수 있습니다.</p><p style="text-align:justify;"> </p><pre><code class="language-javascript">const executor = (resolve, reject) => { setTimeout(() => { resolve('성공'); reject('실패'); }, 3000); }; const promise = new Promise(executor); promise.then( (result) => { console.log(result); }, (error) => { console.log(error); } );</code></pre><p style="text-align:justify;"> </p><p style="text-align:justify;">이처럼 프로미스 객체에는 then()이라는 메서드를 사용할 수 있는데요. then 메서드를 사용하면 프로미스 객체의 메서드로 콜백함수를 전달할 수 있습니다. then 메서드의 첫 번째 인수는 프로미스 객체가 fulfilled 상태일 때 실행되는 함수이고, 두 번째 인수는 프로미스 객체가 rejected 상태일 때 실행되는 함수입니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">따라서 위의 코드를 실행하고 비동기 작업이 성공한다면, 첫 번째 인수로 전달된 함수를 통해 ‘성공’이 출력되고, 비동기 작업이 실패된다면 ‘실패’라는 값이 출력되겠네요. 비동기 작업은 순차적인 작업이 아니기 때문에 성공할 수도, 실패할 수도 있습니다. 따라서 이렇게 프로미스 객체를 활용하면 비동기 작업을 더 효과적으로 다룰 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>마치며</strong></h3><p style="text-align:justify;">글을 마무리하면서 비동기 프로그래밍에 대해 다시 한번 정리해 보겠습니다. 비동기 프로그래밍은 자바스크립트의 동작 방식에서 중요한 역할을 합니다. 기본적으로 자바스크립트는 싱글 스레드에서 실행되기 때문에 동기 방식으로만 코드를 실행하면, 시간이 오래 걸리는 작업이 전체 프로그램의 흐름을 멈추게 할 수 있습니다. 이를 해결하기 위해 비동기 처리를 활용하면 코드가 멈추지 않고, 자연스럽게 다음 작업을 수행할 수 있어 사용자에게 좋은 경험을 제공할 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">또한 이번 글에서는 setTimeout과 프로미스 객체를 이용한 비동기 처리 방법을 살펴봤는데요. setTimeout은 특정 시간 후에 작업을 실행할 수 있도록 도와주는 가장 간단한 방법이며, 프로미스 객체는 비동기 작업의 완료 및 실패 상태를 관리하는 더욱 강력한 도구입니다. 이러한 개념을 이해하면, 다음 글에서 다루게 될 프로미스 객체의 “콜백 지옥” 문제를 해결할 수 있고, async/await과 같은 최신 비동기 방식도 쉽게 익힐 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">아직 자바스크립트의 비동기에 대해 전부 다루지 않았기 때문에, 다음 글에서 프로미스 객체에 대해 조금 더 깊이 배워보려고 합니다. 콜백 지옥이란 무엇인지, 그리고 async/await 문법과 함께 API를 호출하는 방법까지 더 자세히 살펴보겠습니다.</p><hr><p style="text-align:justify;"><참고></p><ul><li><a href="https://ko.javascript.info/promise-basics"><u>javascript.info</u></a></li><li><a href="https://inf.run/7AC37"><u>인프런</u></a></li><li><a href="https://developer.mozilla.org/ko/docs/Learn_web_development/Extensions/Async_JS/Introducing"><u>[mdn web docs] Introducing asynchronous JavaScript</u></a></li></ul><p style="text-align:center;"> </p><p style="text-align:center;"><span style="color:#999999;">©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.</span></p>