diff options
Diffstat (limited to 'files/ko/conflicting')
3 files changed, 1173 insertions, 0 deletions
diff --git a/files/ko/conflicting/learn/javascript/asynchronous/index.html b/files/ko/conflicting/learn/javascript/asynchronous/index.html new file mode 100644 index 0000000000..19f9ffd02d --- /dev/null +++ b/files/ko/conflicting/learn/javascript/asynchronous/index.html @@ -0,0 +1,599 @@ +--- +title: 'Cooperative asynchronous JavaScript: Timeouts and intervals' +slug: conflicting/Learn/JavaScript/Asynchronous +translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +original_slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</div> + +<p class="summary">이 장에서는 자바스크립트가 설정된 시간이 경과하거나 혹은 정해진 시간 간격(예 : 초당 설정된 횟수)으로 비동기 코드를 작동하는 전형적인 방법을 살펴본다. 그리고 이 방법들이 어떤 것에 유용한지 얘기해 보고, 그 본질적인 문제에 대해 살펴본다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Basic computer literacy, a reasonable understanding of JavaScript fundamentals.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand asynchronous loops and intervals and what they are useful for.</td> + </tr> + </tbody> +</table> + +<h2 id="Introduction">Introduction</h2> + +<p>오랜 시간 동안 웹플랫폼은 자바스크립트 프로그래머가 일정한 시간이 흐른 뒤에 비동기적 코드를 실행할 수 있게하는 다양한 함수들을 제공해 왔다. 그리고 프로그래머가 중지시킬 때까지 코드 블록을 반복적으로 실행하기 위한 다음과 같은 함수들이 있다.</p> + +<dl> + <dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code></dt> + <dd>특정 시간이 경과한 뒤에 특정 코드블록을 한번 실행한다.</dd> + <dt></dt> + <dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a></code></dt> + <dd>각각의 호출 간에 일정한 시간 간격으로 특정 코드블록을 반복적으로 실행한다.</dd> + <dt><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code></dt> + <dd>setInterval()의 최신 버전; 그 코드가 실행되는 환경에 관계없이 적절한 프레임 속도로 애니메이션을 실행시키면서, 브라우저가 다음 화면을 보여주기 전에 특정 코드블록을 실행한다.</dd> +</dl> + +<p>이 함수들에 의해 설정된 비동기 코드는 메인 스레드에서 작동한다. 그러나 프로세서가 이러한 작업을 얼마나 많이 수행하는지에 따라, 코드가 반복되는 중간에 다른 코드를 어느 정도 효율적으로 실행할 수 있다. 어쨌든 이러한 함수들은 웹사이트나 응용 프로그램에서 일정한 애니메이션 및 기타 배경 처리를 실행하는 데 사용된다. 다음 섹션에서는 그것들을 어떻게 사용할 수 있는지 보여줄 것이다.</p> + +<h2 id="setTimeout">setTimeout()</h2> + +<p>앞에서 언급했듯이 setTimeout ()은 지정된 시간이 경과 한 후 특정 코드 블록을 한 번 실행한다. 그리고 다음과 같은 파라미터가 필요하다.:</p> + +<ul> + <li>실행할 함수 또는 다른 곳에 정의 된 함수 참조.</li> + <li>코드를 실행하기 전에 대기 할 밀리세컨드 단위의 시간 간격 (1000밀리세컨드는 1 초)을 나타내는 숫자. 값을 0으로 지정하면(혹은 이 값을 모두 생략하면) 함수가 즉시 실행된다. 왜 이 파라미터를 수행해야 하는지도 좀 더살펴 볼 것이다. </li> + <li>함수가 실행될 때 함수에 전달해야할 파라미터를 나타내는 0이상의 값.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note:</strong> 타임아웃 콜백은 단독으로 실행되지 않기 때문에 지정된 시간이 지난 그 시점에 정확히 콜백 될 것이라는 보장은 없다. 그보다는 최소한 그 정도의 시간이 지난 후에 호출된다. 메인 스레드가 실행해야 할 핸들러를 찾기 위해 이런 핸들러들을 살펴보는 시점에 도달할 때까지 타임아웃 핸들러를 실행할 수 없다.</p> +</div> + +<p>아래 예제에서 브라우저는 2초가 지나면 익명의 함수를 실행하고 경보 메시지를 띄울 것이다. (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see it running live</a>, and <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see the source code</a>):</p> + +<pre class="brush: js">let myGreeting = setTimeout(function() { + alert('Hello, Mr. Universe!'); +}, 2000)</pre> + +<p>지정한 함수가 꼭 익명일 필요는 없다. 함수에 이름을 부여 할 수 있고, 다른 곳에서 함수를 정의하고 setTimeout ()에 참조(reference)를 전달할 수도 있다. 아래 코드는 위의 코드와 같은 실행 결과를 얻을 수 있다. </p> + +<pre class="brush: js">// With a named function +let myGreeting = setTimeout(function sayHi() { + alert('Hello, Mr. Universe!'); +}, 2000) + +// With a function defined separately +function sayHi() { + alert('Hello Mr. Universe!'); +} + +let myGreeting = setTimeout(sayHi, 2000);</pre> + +<p>예를 들자면 timeout 함수와 이벤트에 의해 중복 호출되는 함수를 사용하려면 이 방법이 유용할 수 있다. 이 방법은 코드라인을 깔끔하게 정리하는 데 도움을 준다. 특히 timeout 콜백의 코드라인이 여러 줄이라면 더욱 그렇다. </p> + +<p>setTimeout ()은 나중에 타임아웃을 할 경우에 타임아웃을 참조하는데 사용하는 식별자 값을 리턴한다. 그 방법을 알아 보려면 아래{{anch("Clearing timeouts")}}을 참조하세요.</p> + +<h3 id="setTimeout_함수에_매개변수parameter_전달">setTimeout () 함수에 매개변수(parameter) 전달</h3> + +<p>setTimeout () 내에서 실행되는 함수에 전달하려는 모든 매개변수는 setTimeout () 매개변수 목록 끝에 추가하여 전달해야 한다. 아래 예제처럼, 이전 함수를 리팩터링하여 전달된 매개변수의 사람 이름이 추가된 문장을 표시할 수 있다.</p> + +<pre class="brush: js">function sayHi(who) { + alert('Hello ' + who + '!'); +}</pre> + +<p>Say hello의 대상이 되는 사람이름은 <code>setTimeout()의 세번째 매개변수로 함수에 전달된다.</code></p> + +<pre class="brush: js">let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');</pre> + +<h3 id="timeout_취소">timeout 취소</h3> + +<p>마지막으로 타임아웃이 생성되면(setTimeout()이 실행되면) 특정시간이 경과하기 전에 <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout">clearTimeout()</a></code>을 호출하여 타임아웃을 취소할 수 있다. <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout">clearTimeout()</a></code>은 <code>setTimeout()</code>콜의 식별자를 매개변수로 <code>setTimeout()</code>에 전달한다. 위 예제의 타임아웃을 취소하려면 아래와 같이 하면 된다.</p> + +<pre class="brush: js">clearTimeout(myGreeting);</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 인사를 할 사람의 이름을 설정하고 별도의 버튼을 사용하여 인사말을 취소 할 수있는 약간 더 복잡한 폼양식 예제인 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/greeter-app.html">greeter-app.html</a> 을 참조하세요.</p> +</div> + +<h2 id="setInterval">setInterval()</h2> + +<p>setTimeout ()은 일정 시간이 지난 후 코드를 한 번 실행해야 할 때 완벽하게 작동합니다. 그러나 애니메이션의 경우와 같이 코드를 반복해서 실행해야 할 경우 어떨까요?</p> + +<p>이럴 경우에 setInterval ()이 필요합니다. setInterval ()은 setTimeout ()과 매우 유사한 방식으로 작동합니다. 다만 setTimeout ()처럼 첫 번째 매개 변수(함수)가 타임아웃 후에 한번 실행되는게 아니라 두 번째 매개 변수에 주어진 시간까지 반복적으로 실행되는 것이 차이점입니다. setInterval() 호출의 후속 파라미터로 실행 중인 함수에 필요한 파라미터를 전달할 수도 있다.</p> + +<p>예를 들어 봅시다. 다음 함수는 새 Date() 객체를 생성한 후에 ToLocaleTimeString()을 사용하여 시간데이터를 문자열로 추출한 다음 UI에 표시합니다. 그리고 setInterval()을 사용하여 초당 한 번 함수(displayTime)를 실행하면 초당 한 번 업데이트되는 디지털 시계와 같은 효과를 만들어냅니다.(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see this live</a>, and also <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see the source</a>): </p> + +<pre class="brush: js">function displayTime() { + let date = new Date(); + let time = date.toLocaleTimeString(); + document.getElementById('demo').textContent = time; +} + +const createClock = setInterval(displayTime, 1000);</pre> + +<p>setTimeout()과 같이 setInterval()도 식별자 값을 리턴하여 나중에 interval을 취소해야 할 때 사용한다.</p> + +<h3 id="interval_취소">interval 취소</h3> + +<p>setInterval ()은 아무 조치를 취하지 않으면 끊임없이 계속 실행됩니다. 이 상태를 중지하는 방법이 필요합니다. 그렇지 않으면 브라우저가 추가 작업을 완료 할 수 없거나, 현재 처리 중인 애니메이션이 완료되었을 때 오류가 발생할 수 있습니다. setTimeout()과 같은 방식으로 setInterval () 호출에 의해 반환 된 식별자를 clearInterval () 함수에 전달하여 이 작업을 취소할수 있습니다.</p> + +<pre class="brush: js">const myInterval = setInterval(myFunction, 2000); + +clearInterval(myInterval);</pre> + +<h4 id="능동학습_자신만의_스톱워치를_만들기.">능동학습 : 자신만의 스톱워치를 만들기.</h4> + +<p>위에서 모두 설명해 드렸으니, 과제를 드리겠습니다. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">setInterval-clock.html</a> 예제를 수정하여 자신만의 간단한 스톱워치를 만들어 보세요.</p> + +<p>위에서 설명(디지털 시계)한 것처럼 시간을 표시해야 하고, 이 예제에서는 아래 기능을 추가하세요.</p> + +<ul> + <li>스톱워치를 실행시키는 "Start" 버튼. </li> + <li>일시정지나 종료시키는 "Stop" 버튼.</li> + <li>시간을 0으로 설정하는 "Reset" 버튼.</li> + <li>실제 시간을 표시하지 말고 경과된 시간을 초단위로 표시..</li> +</ul> + +<p>몇가지 힌트를 드립니다.</p> + +<ul> + <li>버튼 마크업은 여러분이 원하는 대로 구성하고 스타일링 하세요. 자바스크립트 버튼 레퍼런스를 가져올 수 있는 후크를 포함한 semantic HTML을 사용하세요.</li> + <li> 0에서 시작하여 상수 루프를 이용하여 1 초 단위로 증가하는 변수를 작성해 보세요.</li> + <li>앞서 설명한 Date () 객체를 사용하지 않고 이 예제를 작성하는 것이 더 쉽지만 정확도는 떨어집니다. 정확히 1000ms 후에 콜백이 시작된다고 보장 할 수는 없기 때문입니다. 보다 정확한 방법은 startTime = Date.now ()를 실행하여 사용자가 시작 버튼을 클릭했을 때 정확한 timestamp을 얻은 다음 Date.now ()-startTime을 실행하여 시간(milliseconds)을 얻어 냅니다.</li> + <li>시, 분, 초 단위의 시간을 별도의 변수를 통해 계산한 다음 각 루프 반복 후에 문자열로 함께 표시해 보세요. 두 번째 카운터에서 하나씩 해결할 수 있습니다.</li> + <li>어떻게 계산할 것인지 생각해 봅시다. + <ul> + <li>1시간은 3600초 입니다.</li> + <li>분단위 시간은 시간을 60으로 나눈 값에서 시간단위를 제거한 값입니다. 그리고 초단위 시간은 그 값에서 분단위 시간을 제거한 값입니다(예 :3700초를 60초로 나누면 3600초(시단위) + 100초(분단위), 분단위를 다시 60으로 나누면 60초(분단위) + 40초(초단위))</li> + </ul> + </li> + <li>시간값이 10밀리세컨드이하면 화면의 시간을 0으로 표시하세요. 더 일반적인 시계처럼 보일 겁니다.</li> + <li>스톱워치를 정지시키기 위해 interval을 취소해 보세요. 스톱워치를 리셋하기 위해 시간 카운터를 0으로 설정하고, interval을 취소한 후에 화면을 즉시 업데이트 하세요.</li> + <li>스타트 버튼이 클릭되면 버튼을 즉시 비활성화 시키고 스톱/리셋버튼이 클릭되면 다시 활성화해 보세요. 그렇지 않고 스타트 버튼이 여러번 클릭되면 setInterval()이 중복 실행되어서 실행 오류가 발생할 수 있습니다.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you get stuck, you can <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">find our version here</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">source code</a> also).</p> +</div> + +<h2 id="setTimeout과_setInterval에서_주의해야할_것_들">setTimeout()과 setInterval()에서 주의해야할 것 들</h2> + +<p>setTimeout()과 setInterval()에는 몇가지 주의해야 할 것들이 있습니다. 어떤 것인지 한번 살펴보겠습니다.</p> + +<h3 id="순환_timeouts">순환 timeouts</h3> + +<p>setTimeout()을 사용하는 또 다른 방법입니다. 바로 setInterval()을 사용하는 대신 setTimeout()을 이용해 같은코드를 반복적으로 실행시키는 방법입니다.</p> + +<p>The below example uses a recursive <code>setTimeout()</code> to run the passed function every 100 milliseconds: 아래 예제에서는 setTimeout()이 주어진 함수를 100밀리세컨드마다 실행합니다.</p> + +<pre class="brush: js">let i = 1; + +setTimeout(function run() { + console.log(i); + i++; + setTimeout(run, 100); +}, 100);</pre> + +<p>위 예제를 아래 예제와 비교해 보세요. 아래 예제는 setInterval()을 사용하여 같은 결과를 얻을 수 있습니다.</p> + +<pre class="brush: js">let i = 1; + +setInterval(function run() { + console.log(i); + i++ +}, 100);</pre> + +<h4 id="그렇다면_순환_setTimeout과_setInterval은_어떻게_다를까요">그렇다면 순환 setTimeout()과 setInterval()은 어떻게 다를까요?</h4> + +<p>두 방법의 차이는 미묘합니다.</p> + +<ul> + <li>순환 setTimeout ()은 실행과 실행 사이에 동일한 지연을 보장합니다. 위의 경우 100ms입니다. 코드가 실행 된 후 다시 실행되기 전에 100 ms 동안 대기하므로 간격은 코드 실행 시간에 관계없이 동일합니다.</li> + <li>setInterval ()을 사용하는 예제는 약간 다르게 작동합니다. 설정된 간격에는 실행하려는 코드를 실행하는 데 걸리는 시간이 포함됩니다. 코드를 실행하는 데 40ms가 걸리다면 간격이 60ms에 불과합니다.</li> + <li>setTimeout ()을 재귀적으로 사용할 때 각 반복은 다음 반복을 실행하기 전에 또 하나의 대기시간을 설정합니다. 즉, setTimeout ()의 두 번째 매개 변수의 값은 코드를 다시 실행하기 전에 대기 할 또 하나의 시간을 지정합니다.</li> +</ul> + +<p>코드가 지정한 시간 간격보다 실행 시간이 오래 걸리면 순환 setTimeout ()을 사용하는 것이 좋습니다. 이렇게하면 코드 실행 시간에 관계없이 실행 간격이 일정하게 유지되어 오류가 발생하지 않습니다.</p> + +<h3 id="즉시_timeouts">즉시 timeouts</h3> + +<p>setTimeout()의 값으로 0을 사용하면 메인 코드 스레드가 실행된 후에 가능한 한 빨리 지정된 콜백 함수의 실행을 예약할 수 있다.</p> + +<p>예를 들어 아래 코드 (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/zero-settimeout.html">see it live</a>) 는 "Hello"가 포함된 alert를 출력 한 다음 첫 번째 경고에서 OK를 클릭하자마자 "World"가 포함된 alert를 출력합니다.</p> + +<pre class="brush: js">setTimeout(function() { + alert('World'); +}, 0); + +alert('Hello');</pre> + +<p>이것은 모든 메인 스레드의 실행이 완료되자마자 실행되도록 코드 블록을 설정하려는 경우 유용할 할 수 있습니다. 비동기 이벤트 루프에 배치하면 곧바로 실행될 겁니다.</p> + +<h3 id="clearTimeout_와_clearInterval의_취소기능">clearTimeout() 와 clearInterval()의 취소기능</h3> + +<p>clearTimeout ()과 clearInterval ()은 모두 동일한 entry를 사용하여 대상 메소드(setTimeout () 또는 setInterval ())을 취소합니다. 흥미롭게도 이는 setTimeout () 또는 setInterval ()을 지우는 데 clearTimeout ()과 clearInterval ()메소드 어느 것을 사용해도 무방합니다.</p> + +<p>그러나 일관성을 유지하려면 clearTimeout ()을 사용하여 setTimeout () 항목을 지우고 clearInterval ()을 사용하여 setInterval () 항목을 지우십시오. 혼란을 피하는 데 도움이됩니다.</p> + +<h2 id="requestAnimationFrame">requestAnimationFrame()</h2> + +<p>requestAnimationFrame ()은 브라우저에서 애니메이션을 효율적으로 실행하기 위해 만들어진 특수한 반복 함수입니다. 근본적으로 setInterval ()의 최신 버전입니다. 브라우저가 다음에 디스플레이를 다시 표시하기 전에 지정된 코드 블록을 실행하여 애니메이션이 실행되는 환경에 관계없이 적절한 프레임 속도로 실행될 수 있도록합니다.</p> + +<p>setInterval ()을 사용함에 있어 알려진 문제점을 개선하기위해 만들어졌습니다. 예를 들어 장치에 최적화 된 프레임 속도로 실행되지 않는 문제, 때로는 프레임을 빠뜨리는 문제, 탭이 활성 탭이 아니거나 애니메이션이 페이지를 벗어난 경우에도 계속 실행되는 문제 등등이다 . <a href="http://creativejs.com/resources/requestanimationframe/index.html">CreativeJS에서 이에 대해 자세히 알아보십시오.</a></p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: requestAnimationFrame() 사용에 관한 예제들은 이 코스의 여러곳에서 찾아볼 수 있습니다. <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a> 와 <a href="/en-US/docs/Learn/JavaScript/Objects/Object_building_practice">Object building practice</a> 의 예제를 찾아 보세요.</p> +</div> + +<p>이 메소드는 화면을 다시 표시하기 전에 호출 할 콜백을 인수로 사용합니다. 이것이 일반적인 패턴입니다. 아래는 사용예를 보여줍니다.</p> + +<pre class="brush: js">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>그 발상은 애니메이션을 업데이트하는 함수 (예 : 스프라이트 이동, 스코어 업데이트, 데이터 새로 고침 등)를 정의한 후 그것을 호출하여 프로세스를 시작하는 것입니다. 함수 블록의 끝에서 매개 변수로 전달 된 함수 참조를 사용하여 requestAnimationFrame ()을 호출하면 브라우저가 다음 화면을 재표시할 때 함수를 다시 호출하도록 지시합니다. 그런 다음 requestAnimationFrame ()을 반복적으로 호출하므로 계속 실행되는 것입니다.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 어떤 간단한 DOM 애니메이션을 수행하려는 경우, <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS Animations</a> 은 JavaScript가 아닌 브라우저의 내부 코드로 직접 계산되므로 속도가 더 빠릅니다. 그러나 더 복잡한 작업을 수행하고 DOM 내에서 직접 액세스 할 수 없는 객체(예 :<a href="/en-US/docs/Web/API/Canvas_API">2D Canvas API</a> or <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> objects)를 포함하는 경우 대부분의 경우 requestAnimationFrame ()이 더 나은 옵션입니다. </p> +</div> + +<h3 id="여러분의_애니메이션의_작동속도는_얼마나_빠른가요">여러분의 애니메이션의 작동속도는 얼마나 빠른가요?</h3> + +<p>부드러운 애니메이션을 구현은 직접적으로 프레임 속도에 달려 있으며, 프레임속도는 초당 프레임 (fps)으로 측정됩니다. 이 숫자가 높을수록 애니메이션이 더 매끄럽게 보입니다.</p> + +<p>일반적으로 화면 재생률는 60Hz이므로 웹 브라우저를 사용할 때 여러분이 설정할 수 있는 가장 빠른 프레임 속도는 초당 60 프레임 (FPS)입니다. 이 속도보다 빠르게 설정하면 과도한 연산이 실행되어 화면이 더듬거리고 띄엄띄엄 표시될 수 있다. 이런 현상을 프레임 손실 또는 쟁크라고 한다. </p> + +<p>재생률 60Hz의 모니터에 60FPS를 달성하려는 경우 각 프레임을 렌더링하기 위해 애니메이션 코드를 실행하려면 약 16.7ms(1000/60)가 필요합니다. 그러므로 각 애니메이션 루프를 통과 할 때마다 실행하려고 하는 코드의 양을 염두에 두어야합니다.</p> + +<p>requestAnimationFrame()은 불가능한 경우에도 가능한한 60FPS 값에 가까워 지려고 노력합니다. 실제로 복잡한 애니메이션을 느린 컴퓨터에서 실행하는 경우 프레임 속도가 떨어집니다. requestAnimationFrame ()은 항상 사용 가능한 것을 최대한 활용합니다.</p> + +<h3 id="requestAnimationFrame이_setInterval_setTimeout과_다른점은">requestAnimationFrame()이 setInterval(), setTimeout()과 다른점은?</h3> + +<p>requestAnimationFrame () 메소드가 이전에 살펴본 다른 메소드와 어떻게 다른지에 대해 조금 더 이야기하겠습니다. 위의 코드를 다시 살펴보면;</p> + +<pre class="brush: js">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>setInterval()을 사용하여 위와 같은 작업을 하는 방법을 살펴봅시다.</p> + +<pre class="brush: js">function draw() { + // Drawing code goes here +} + +setInterval(draw, 17);</pre> + +<p>앞에서언급했듯이 requestAnimationFrame ()은 시간 간격을 지정하지 않습니다. requestAnimationFrame ()은 현재 상황에서 최대한 빠르고 원활하게 실행됩니다. 어떤 이유로 애니메이션이 화면에 표시되지 않으면 브라우저는 그 애니메이션을 실행하는 데 시간을 낭비하지 않습니다.</p> + +<p>반면에 setInterval ()은 특정 시간간격을 필요로 합니다. 1000 ms/60Hz 계산을 통해 최종값 16.6에 도달 한 후 반올림(17)했습니다. 이때 반올림하는 것이 좋습니다. 그 이유는 반내림(16)을하면 60fps보다 빠르게 애니메이션을 실행하려고 하게 되지만 애니메이션의 부드러움에 아무런 영향을 미치지 않기 때문입니다. 앞에서 언급했듯이 60Hz가 표준 재생률입니다.</p> + +<h3 id="timestamp를_포함하기">timestamp를 포함하기</h3> + +<p>requestAnimationFrame() 함수에 전달 된 실제 콜백에는 requestAnimationFrame()이 실행되기 시작한 이후의 시간을 나타내는 timestamp를 매개변수로 제공할 수 있습니다. 장치 속도에 관계없이 특정 시간과 일정한 속도로 작업을 수행할 수 있으므로 유용합니다. 사용하는 일반적인 패턴은 다음과 같습니다.</p> + +<pre class="brush: js">let startTime = null; + +function draw(timestamp) { + if(!startTime) { + startTime = timestamp; + } + + currentTime = timestamp - startTime; + + // Do something based on current time + + requestAnimationFrame(draw); +} + +draw();</pre> + +<h3 id="브라우저_지원">브라우저 지원</h3> + +<p>requestAnimationFrame()은 setInterval() / setTimeout()보다 좀 더 최신 브라우저에서 지원됩니다. 가장 흥미롭게도 Internet Explorer 10 이상에서 사용할 수 있습니다. 따라서 별도의 코드로 이전 버전의 IE를 지원해야할 필요가 없다면, requestAnimationFrame()을 사용하지 않을 이유가 없습니다.</p> + +<h3 id="간단한_예">간단한 예</h3> + +<p>지금까지 이론적으로는 충분히 살펴보았습니다. 그러면 직접 requestAnimationFrame() 예제를 작성해 봅시다. 우리는 간단한 "스피너 애니메이션"을 만들 것입니다. 여러분들이 앱 사용중 서버 과부하일 때 이것을 자주 보았을 겁니다.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 실제로는 CSS 애니메이션을 사용하여 이러한 종류의 간단한 애니메이션을 실행해야 합니다. 그러나 이러한 종류의 예제는 requestAnimationFrame() 사용법을 보여주는 데 매우 유용하며, 각 프레임에서 게임의 디스플레이를 업데이트하는 것과 같이 좀 더 복잡한 작업을 수행 할 때 이러한 종류의 기술을 사용하는 것이 좋습니다.</p> +</div> + +<ol> + <li> + <p>먼저 HTML 템플릿을 <a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">여기</a>에서.가져옵니다.</p> + </li> + <li> + <p><body>안에 빈 <div> 요소를 삽입합니다. 그리고 ↻캐릭터를 그 안에 추가합니다. 이 예제에서 이 원형 화살표가 회전하게됩니다.</p> + </li> + <li> + <p>아래 CSS를 HTML 템플릿에 여러분이 원하는 방식으로 적용하세요. 이 CSS는 페이지 배경을 빨간색으로, <body>의 height를 html height의 100%로 설정합니다. 그리고 <div>를 수직, 수평으로 <body> 중앙에 위치 시킵니다.</p> + + <pre class="brush: css">html { + background-color: white; + height: 100%; +} + +body { + height: inherit; + background-color: red; + margin: 0; + display: flex; + justify-content: center; + align-items: center; +} + +div { + display: inline-block; + font-size: 10rem; +}</pre> + </li> + <li> + <p></body> 태크 위에 <script>를 추가하세요.</p> + </li> + <li> + <p><script> 안에 아래 자바스크립트 코드를 추가하세요. 여기에서 <div>의 참조를 상수로 저장하고, rotateCount 변수를 0으로 설정하고, 나중에 requestAnimationFrame()의 시작 시간을 저장할 startTime 변수를 null로 설정하고, 그리고 requestAnimationFrame() 콜의 참조를 저장할 초기화하지 않은 rAF 변수를 선언합니다.</p> + + <pre class="brush: js">const spinner = document.querySelector('div'); +let rotateCount = 0; +let startTime = null; +let rAF; +</pre> + </li> + <li> + <p>그 아래에 draw() 함수를 추가합니다. 이 함수는 timestamp 매개변수를 포함하는 애니메이션 코드 작성에 사용됩니다.</p> + + <pre class="brush: js">function draw(timestamp) { + +}</pre> + </li> + <li> + <p>draw() 함수안데 다음 코드를 추가합니다. if 조건문으로 startTime이 정의되지 않았다면 startTime을 정의합니다 (루프 반복의 첫번째에만 작동합니다). 그리고 스피너를 회전시키는 rotateCount 변수값을 설정합니다(현재 timestamp는 시작 timestamp를 3으로 나눈 것이라서 그리 빠르지 않습니다).</p> + + <pre class="brush: js"> if (!startTime) { + startTime = timestamp; + } + + rotateCount = (timestamp - startTime) / 3; +</pre> + </li> + <li> + <p>draw() 함수 안의 이전 코드 아래에 다음 블록을 추가합니다. 이렇게하면 rotateCount 값이 359보다 큰지 확인합니다 (예 : 360, 완전한 원). 그렇다면 값을 모듈러 360 (즉, 값을 360으로 나눌 때 남은 나머지)으로 설정하여 원 애니메이션이 합리적인 낮은 값으로 중단없이 계속 될 수 있습니다. 꼭 이렇게 해야되는 것은 아니지만 "128000도" 같은 값보다는 0~359 도의 값으로 작업하는 것이 더 쉽습니다.</p> + + <pre class="brush: js">if (rotateCount > 359) { + rotateCount %= 360; +}</pre> + </li> + <li>다음으로 아래 코드를 추가하세요. 실제 스피너를 회전시키는 코드입니다. + <pre class="brush: js">spinner.style.transform = 'rotate(' + rotateCount + 'deg)';</pre> + </li> + <li> + <p> draw() 함수 제일 아래에 다음 코드를 추가합니다. 이 코드는 모든 작업의 키 포인트입니다. draw() 함수를 매개변수로 가져오는 requestAnimationFrame() 콜을 저장하기 위해 앞에서 정의한 rAF 변수를 설정합니다. 이 코드는 애니메이션을 실행시키고, 가능한한 60fps에 근사하게 draw() 함수를 계속 실행합니다.</p> + + <pre class="brush: js">rAF = requestAnimationFrame(draw);</pre> + </li> +</ol> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You can find this <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">example live on GitHub</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">source code</a> also).</p> +</div> + +<h3 id="requestAnimationFrame_call의_취소">requestAnimationFrame() call의 취소</h3> + +<p>cancelAnimationFrame() 메소드를 호출하여 requestAnimationFrame()을 취소할 수 있다. (접두어가 "clear"가 아니고 "cancel"임에 유의) requestAnimationFrame()에 의해서 리턴된 rAF 변수값을 전달받아 취소한다. </p> + +<pre class="brush: js">cancelAnimationFrame(rAF);</pre> + +<h3 id="Active_learning_Starting_and_stopping_our_spinner">Active learning: Starting and stopping our spinner</h3> + +<p>In this exercise, we'd like you to test out the <code>cancelAnimationFrame()</code> method by taking our previous example and updating it, adding an event listener to start and stop the spinner when the mouse is clicked anywhere on the page.</p> + +<p>Some hints:</p> + +<ul> + <li>A <code>click</code> event handler can be added to most elements, including the document <code><body></code>. It makes more sense to put it on the <code><body></code> element if you want to maximize the clickable area — the event bubbles up to its child elements.</li> + <li>You'll want to add a tracking variable to check whether the spinner is spinning or not, clearing the animation frame if it is, and calling it again if it isn't.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Try this yourself first; if you get really stuck, check out of our <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">live example</a> and <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">source code</a>.</p> +</div> + +<h3 id="Throttling_a_requestAnimationFrame_animation">Throttling a requestAnimationFrame() animation</h3> + +<p>One limitation of <code>requestAnimationFrame()</code> is that you can't choose your frame rate. This isn't a problem most of the time, as generally you want your animation to run as smoothly as possible, but what about when you want to create an old school, 8-bit-style animation?</p> + +<p>This was a problem for example in the Monkey Island-inspired walking animation from our <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing Graphics</a> article:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p> + +<p>In this example we have to animate both the position of the character on the screen, and the sprite being shown. There are only 6 frames in the sprite's animation; if we showed a different sprite frame for every frame displayed on the screen by <code>requestAnimationFrame()</code>, Guybrush would move his limbs too fast and the animation would look ridiculous. We therefore throttled the rate at which the sprite cycles its frames using the following code:</p> + +<pre class="brush: js">if (posX % 13 === 0) { + if (sprite === 5) { + sprite = 0; + } else { + sprite++; + } +}</pre> + +<p>So we are only cycling a sprite once every 13 animation frames. OK, so it's actually about every 6.5 frames, as we update <code>posX</code> (character's position on the screen) by two each frame:</p> + +<pre class="brush: js">if(posX > width/2) { + newStartPos = -((width/2) + 102); + posX = Math.ceil(newStartPos / 13) * 13; + console.log(posX); +} else { + posX += 2; +}</pre> + +<p>This is the code that works out how to update the position in each animation frame.</p> + +<p>The method you use to throttle your animation will depend on your particular code. For example, in our spinner example we could make it appear to move slower by only increasing our <code>rotateCount</code> by one on each frame instead of two.</p> + +<h2 id="Active_learning_a_reaction_game">Active learning: a reaction game</h2> + +<p>For our final section of this article, we'll create a 2-player reaction game. Here we have two players, one of whom controls the game using the <kbd>A</kbd> key, and the other with the <kbd>L</kbd> key.</p> + +<p>When the <em>Start</em> button is pressed, a spinner like the one we saw earlier is displayed for a random amount of time between 5 and 10 seconds. After that time, a message will appear saying "PLAYERS GO!!" — once this happens, the first player to press their control button will win the game.</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}</p> + +<p>Let's work through this.</p> + +<ol> + <li> + <p>First of all, download the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game-starter.html">starter file for the app</a> — this contains the finished HTML structure and CSS styling, giving us a game board that shows the two players' information (as seen above), but with the spinner and results paragraph displayed on top of one another. We just have to write the JavaScript code.</p> + </li> + <li> + <p>Inside the empty {{htmlelement("script")}} element on your page, start by adding the following lines of code that define some constants and variables we'll need in the rest of the code:</p> + + <pre class="brush: js">const spinner = document.querySelector('.spinner p'); +const spinnerContainer = document.querySelector('.spinner'); +let rotateCount = 0; +let startTime = null; +let rAF; +const btn = document.querySelector('button'); +const result = document.querySelector('.result');</pre> + + <p>In order, these are:</p> + + <ol> + <li>A reference to our spinner, so we can animate it.</li> + <li>A reference to the {{htmlelement("div")}} element that contains the spinner, used for showing and hiding it.</li> + <li>A rotate count — how much we want to show the spinner rotated on each frame of the animation.</li> + <li>A null start time — will be populated with a start time when the spinner starts spinning.</li> + <li>An uninitialized variable to later store the {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} call that animates the spinner.</li> + <li>A reference to the Start button.</li> + <li>A reference to the results paragraph.</li> + </ol> + </li> + <li> + <p>Next, below the previous lines of code, add the following function. This simply takes two numerical inputs and returns a random number between the two. We'll need this to generate a random timeout interval later on.</p> + + <pre class="brush: js">function random(min,max) { + var num = Math.floor(Math.random()*(max-min)) + min; + return num; +}</pre> + </li> + <li> + <p>Next add in the <code>draw()</code> function, which animates the spinner. This is very similar to the version seen in the simple spinner example we looked at earlier:</p> + + <pre class="brush: js">function draw(timestamp) { + if(!startTime) { + startTime = timestamp; + } + + rotateCount = (timestamp - startTime) / 3; + + if(rotateCount > 359) { + rotateCount %= 360; + } + + spinner.style.transform = 'rotate(' + rotateCount + 'deg)'; + rAF = requestAnimationFrame(draw); +}</pre> + </li> + <li> + <p>Now it is time to set up the initial state of the app when the page first loads. Add the following two lines, which simply hide the results paragraph and spinner container using <code>display: none;</code>.</p> + + <pre class="brush: js">result.style.display = 'none'; +spinnerContainer.style.display = 'none';</pre> + </li> + <li> + <p>We'll also define a <code>reset()</code> function, which sets the app back to the original state required to start the game again after it has been played. Add the following at the bottom of your code:</p> + + <pre class="brush: js">function reset() { + btn.style.display = 'block'; + result.textContent = ''; + result.style.display = 'none'; +}</pre> + </li> + <li> + <p>OK, enough preparation. Let's make the game playable! Add the following block to your code. The <code>start()</code> function calls <code>draw()</code> to start the spinner spinning and display it in the UI, hides the <em>Start</em> button so we can't mess up the game by starting it multiple times concurrently, and runs a <code>setTimeout()</code> call that runs a <code>setEndgame()</code> function after a random interval between 5 and 10 seconds has passed. We also add an event listener to our button to run the <code>start()</code> function when it is clicked.</p> + + <pre class="brush: js">btn.addEventListener('click', start); + +function start() { + draw(); + spinnerContainer.style.display = 'block'; + btn.style.display = 'none'; + setTimeout(setEndgame, random(5000,10000)); +}</pre> + + <div class="blockIndicator note"> + <p><strong>Note</strong>: You'll see that in this example we are calling <code>setTimeout()</code> without storing the return value (so not <code>let myTimeout = setTimeout(functionName, interval)</code>). This works and is fine, as long as you don't need to clear your interval/timeout at any point. If you do, you'll need to save the returned identifier.</p> + </div> + + <p>The net result of the previous code is that when the <em>Start</em> button is pressed, the spinner is shown and the players are made to wait a random amount of time before they are then asked to press their button. This last part is handled by the <code>setEndgame()</code> function, which we should define next.</p> + </li> + <li> + <p>So add the following function to your code next:</p> + + <pre class="brush: js">function setEndgame() { + cancelAnimationFrame(rAF); + spinnerContainer.style.display = 'none'; + result.style.display = 'block'; + result.textContent = 'PLAYERS GO!!'; + + document.addEventListener('keydown', keyHandler); + + function keyHandler(e) { + console.log(e.key); + if(e.key === 'a') { + result.textContent = 'Player 1 won!!'; + } else if(e.key === 'l') { + result.textContent = 'Player 2 won!!'; + } + + document.removeEventListener('keydown', keyHandler); + setTimeout(reset, 5000); + }; +}</pre> + + <p>Stepping through this:</p> + + <ol> + <li>First we cancel the spinner animation with {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}} (it is always good to clean up unneeded processes), and hide the spinner container.</li> + <li>Next we display the results paragraph and set its text content to "PLAYERS GO!!" to signal to the players that they can now press their button to win.</li> + <li>We then attach a <code><a href="/en-US/docs/Web/API/Document/keydown_event">keydown</a></code> event listener to our document — when any button is pressed down, the <code>keyHandler()</code> function is run.</li> + <li>Inside <code>keyHandler()</code>, we include the event object as a parameter (represented by <code>e</code>) — its {{domxref("KeyboardEvent.key", "key")}} property contains the key that was just pressed, and we can use this to respond to specific key presses with specific actions.</li> + <li>We first log <code>e.key</code> to the console, which is a useful way of finding out the <code>key</code> value of different keys you are pressing.</li> + <li>When <code>e.key</code> is "a", we display a message to say that Player 1 won, and when <code>e.key</code> is "l", we display a message to say Player 2 won. Note that this will only work with lowercase a and l — if an uppercase A or L is submitted (the key plus <kbd>Shift</kbd>), it is counted as a different key.</li> + <li>Regardless of which one of the player control keys was pressed, we remove the <code>keydown</code> event listener using {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} so that once the winning press has happened, no more keyboard input is possible to mess up the final game result. We also use <code>setTimeout()</code> to call <code>reset()</code> after 5 seconds — as we explained earlier, this function resets the game back to its original state so that a new game can be started.</li> + </ol> + </li> +</ol> + +<p>That's it, you're all done.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you get stuck, check out <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html">our version of the reaction game</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game.html">source code</a> also).</p> +</div> + +<h2 id="Conclusion">Conclusion</h2> + +<p>So that's it — all the essentials of async loops and intervals covered in one article. You'll find these methods useful in a lot of situations, but take care not to overuse them — since these still run on the main thread, heavy and intensive callbacks (especially those that manipulate the DOM) can really slow down a page if you're not careful.</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">Making asynchronous programming easier with async and await</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li> +</ul> diff --git a/files/ko/conflicting/learn/javascript/asynchronous/introducing/index.html b/files/ko/conflicting/learn/javascript/asynchronous/introducing/index.html new file mode 100644 index 0000000000..7e3d68c37d --- /dev/null +++ b/files/ko/conflicting/learn/javascript/asynchronous/introducing/index.html @@ -0,0 +1,160 @@ +--- +title: 일반적인 비동기 프로그래밍 개념 +slug: conflicting/Learn/JavaScript/Asynchronous/Introducing +tags: + - 비동기 + - 비동기 프로그래밍 + - 자바스크립트 +translation_of: Learn/JavaScript/Asynchronous/Concepts +original_slug: Learn/JavaScript/Asynchronous/Concepts +--- +<div>{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}</div> + +<p>이 문서에서는 비동기적 프로그래밍과 관련된 몇개의 개념들을 살펴볼 것입니다. 그리고 이것들이 웹브라우저와 자바스크립트에서 어떻게 보이는지도 살펴볼 것입니다. 이 모듈의 다른 문서들을 공부하기 전에, 이 문서에 나와있는 개념들을 먼저 학습하십시오.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">선행 조건:</th> + <td>기초적인 컴퓨터 언어 능력, Javascript에 대한 기초적인 이해가 필요합니다. </td> + </tr> + <tr> + <th scope="row">학습 목적: </th> + <td>비동기적 프로그래밍이 작동하는 기초적인 원리에 대해 이해하는 것입니다. 그리고 이 개념이 어떻게 웹브라우저와 자바스크립트에서 압도적인 지위를 차지하기 되었는지 알아봅니다. </td> + </tr> + </tbody> +</table> + +<h2 id="비동기적Asynchronous_이란">'비동기적'(Asynchronous) 이란?</h2> + +<p>일반적으로, 프로그램의 코드는 순차적으로 진행됩니다. 한번에 한가지 사건만 발생하면서 말입니다. 만약 어떤 함수의 결과가 다른 함수에 영향을 받는다면, 그 함수는 다른 함수가 끝나고 값을 산출할 때까지 기다려야 합니다. 그리고 그 과정이 끝날 때 까지, 유저의 입장에서 보자면, 전체 프로그램이 모두 멈춘 것처럼 보입니다. </p> + +<p>예를들면, 맥 유저라면 종종 회전하는 무지개색 커서(비치볼)를 본 적이 있을 것입니다. 이 커서는 오퍼레이팅 시스템이 이렇게 말하고 있는 것입니다. "당신이 지금 사용하고 있는 시스템은 지금 멈춰서서 뭔가가 끝나기를 기다려야만 합니다. 그리고 이 과정은 당신이 지금 무슨 일이 일어나고있는지 궁금해 할 만큼 오래 걸리고 있습니다."</p> + +<p><img alt="Multi-colored macOS beachball busy spinner" src="https://mdn.mozillademos.org/files/16577/beachball.jpg" style="display: block; float: left; height: 256px; margin: 0px 30px 0px 0px; width: 250px;"></p> + +<p>이것은 당황스러운 경험이며, 특히 요즘과 같이 컴퓨터가 여러개 프로세서를 돌리는 시대에는 컴퓨터 성능을 효율적으로 쓰지 못하는 처사입니다. 당신이 다른 코어 프로세서에 다른 작업들을 움직이게 하고 작업이 완료되면 알려줄 수 있을 때, 무언가를 기다리는 것은 의미가 없습니다 .그 동안 다른 작업을 수행할 수 있고, 이것이 비동기 프로그래밍의 기본입니다. 이러한 작업을 비동기적으로 실행할 수 있는 API를 제공하는 것은 당신이 사용하고 있는 프로그래밍 환경(웹 개발의 경우 웹브라우저) 에 달려 있습니다.</p> + +<h2 id="Blocking_code">Blocking code</h2> + +<p>비동기 기법은 특히 웹 프로그래밍에 매우 유용합니다. 웹 앱이 브라우저에서 특정 코드를 실행하느라 브라우저에게 제어권을 돌려주지 않으면 브라우저는 마치 정지된 것처럼 보일 수 있습니다. 이러한 현상을 <strong>blocking </strong>이라고 부릅니다. 자세히 정의하자면, 사용자의 입력을 처리하느라 웹 앱이 프로세서에 대한 제어권을 브라우저에게 반환하지 않는 현상 입니다..</p> + +<p>Blocking의 몇 가지 예를 살펴보겠습니다.</p> + +<p>여기 <a href="https://github.com/mdn/learning-area/tree/master/javascript/asynchronous/introducing">simple-sync.html</a> 예시가 있습니다. (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync.html">see it running live</a>), 하나의 버튼에 클릭 이벤트리스너를 지정하여 시간이 오래 걸리는 처리를 하도록하였습니다. (날짜를 천만번 계산하고 마지막에 콘솔에 날짜를 출력합니다.) 그리고 처리가 끝나면 페이지에 간단한 문장을 한 줄 출력합니다. :</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); +btn.addEventListener('click', () => { + let myDate; + for(let i = 0; i < 10000000; i++) { + let date = new Date(); + myDate = date + } + + console.log(myDate); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +});</pre> + +<p>이 예제를 실행할 때 JavaScript 콘솔을 열고 버튼을 클릭하면, 콘솔에 메시지가 출력되기 전 까지 페이지에 문장이 나타나지 않는다는 것을 알 수 있습니다. 코드는 위에서 아래로 순차적으로 실행되며, 아래쪽 코드는 위쪽 코드의 처리가 끝날 때 까지 실행되지 않습니다.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 앞의 예제는 매우 비현실적입니다. 실제 웹 앱에서 날짜를 천만번 계산할 일은 없습니다. 실제로 보여주기 위해 극단적인 예시를 들었을 뿐입니다..</p> +</div> + +<p>두 번째 예제를 살펴보겠습니다. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">simple-sync-ui-blocking.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">see it live</a>), 페이지에 UI가 모두 표시되기 전 까지 사용자의 입력을 막는 좀 더 현실적인 예시입니다. 이번 예시에는 두 가지 버튼을 사용합니다. :</p> + +<ul> + <li>"Fill canvas" 버튼을 클릭하면 {{htmlelement("canvas")}} 태그에 100만개의 파란색 원을 채웁니다. (실행하면 원이 너무 많아서 원이 아니라 배경이 그냥 파란색으로 채워집니다.)</li> + <li>"Click me for alert" 버튼은 사용자에게 메시지를 출력합니다.</li> +</ul> + +<pre class="brush: js notranslate">function expensiveOperation() { + for(let i = 0; i < 1000000; i++) { + ctx.fillStyle = 'rgba(0,0,255, 0.2)'; + ctx.beginPath(); + ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false); + ctx.fill() + } +} + +fillBtn.addEventListener('click', expensiveOperation); + +alertBtn.addEventListener('click', () => + alert('You clicked me!') +);</pre> + +<p>첫 번째 버튼을 클릭한 후 두 번째 버튼을 바로 클릭하면 경고 박스가 나타나지 않는 것을 확인할 수 있습니다. 첫 번째 버튼은 이벤트가 끝나기 전 까지 다음 작동을 막아버립니다.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: OK, 이 예시가 Blocking 효과를 설명하기에는 구리지만, 실제로 개발자가 겪는 일반적인 문제입니다.</p> +</div> + +<p>왜 이런 현상이 발생할까요? 답은 자바스크립트는 기본적으로 <strong>single threaded</strong>이기 때문입니다. 이 시점에서 <strong>threads</strong>의 개념을 소개할 필요가 있겠군요</p> + +<h2 id="Threads">Threads</h2> + +<p><strong>Thread</strong> 는 기본적으로 프로그램이 작업을 완료하는데 사용할 수 있는 단일 프로세스 입니다. 각 스레드는 한 번에 하나의 작업만 수행할 수 있습니다. :</p> + +<pre class="notranslate">Task A --> Task B --> Task C</pre> + +<p>위의 예시처럼 각 작업은 순차적으로 실행되며, 다음 작업을 시작하려면 앞의 작업이 완료되어야 합니다.</p> + +<p>앞서 말했듯이, 많은 컴퓨터들이 현재 여러 개의 CPU코어를 가지고 있기 때문에, 한 번에 여러가지 일을 수행할 수 있습니다. Multiple thread를 지원하는 프로그래밍 언어는 멀티코어 컴퓨터의 CPU를 사용하여 여러 작업을 동시에 처리할 수 있습니다. :</p> + +<pre class="notranslate">Thread 1: Task A --> Task B +Thread 2: Task C --> Task D</pre> + +<h3 id="JavaScript_is_single_threaded">JavaScript is single threaded</h3> + +<p>자바스크립트는 전통적으로 싱글 thread입니다. 컴퓨터가 여러 개의 CPU를 가지고 있어도 <strong>main thread</strong>라 불리는 단일 thread에서만 작업을 실행할 수 있습니다. 위의 예시는 아래처럼 실행됩니다. :</p> + +<pre class="notranslate">Main thread: Render circles to canvas --> Display alert()</pre> + +<p>JavaScript는 이러한 문제를 해결하기 위해 몇 가지 툴을 도입했습니다. <a href="/ko/docs/Web/API/Web_Workers_API">Web workers</a>는 여러 개의 JavaScript 청크를 동시에 실행할 수 있도록 worker라고 불리는 별도의 스레드로 보낼 수 있습니다. 따라서 시간이 오래 걸리는 처리는 woker를 사용해 처리하면 blocking 발생을 막을 수 있습니다..</p> + +<pre class="notranslate"> Main thread: Task A --> Task C +Worker thread: Expensive task B</pre> + +<p>위의 내용을 잘 기억하시고 다음 예제를 살펴보세요. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-worker.html">simple-sync-worker.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-worker.html">see it running live</a>), JavaScript 콘솔을 함께 열어주세요. 이전 예시는 날짜를 천만 번 계산하고 페이지에 문장을 출력했지만, 이번엔 천만번 계산 전 문장을 페이지에 출력해줍니다. 더이상 첫 번째 작업이 두 번째 작업을 차단하지 않습니다.</p> + +<h2 id="Asynchronous_code">Asynchronous code</h2> + +<p>Web worker는 꽤 유용하지만 이들도 한계가 있습니다. 주요한 내용은 Web worker는 {{Glossary("DOM")}} 에 접근할 수 없습니다. — UI를 업데이트하도록 worker에게 어떠한 지시도 직접 할 수 없습니다. 두 번째 예시에서 worker는 100만개의 파란색 원을 만들 수 없습니다. 단순히 숫자만 계산합니다.</p> + +<p>두 번째 문제는 worker에서 실행되는 코드는 차단되지 않지만 동기적으로 실행된다는 것 입니다. 이러한 문제는 함수를 사용할 때 발생합니다. 어떤 함수가 일의 처리를 위해 이전의 여러 프로세스의 결과를 return 받아야 할 경우 입니다. 동기적으로 실행되면 함수 실행에 필요한 매개변수를 받아올 수 없는 경우가 생기므로 함수는 사용자가 원하는 기능을 제대로 실행할 수 없습니다.</p> + +<pre class="notranslate">Main thread: Task A --> Task B</pre> + +<p>이 예시에서 Task A는 서버로부터 이미지를 가져오고 Task B는 그 이미지에 필터를 적용하는 것과 같은 작업을 수행한다고 가정합니다. Task A를 실행하고 결과를 반환할 시간도 없이 Task B를 실행해버리면 에러가 발생할 것 입니다. 왜냐햐면 Task A에서 이미지를 완전히 가져온 상태가 아니기 때문이죠.</p> + +<pre class="notranslate"> Main thread: Task A --> Task B --> |Task D| +Worker thread: Task C -----------> | |</pre> + +<p>이번 예시에선 Task D가 Task B와 Task C의 결과를 모두 사용한다고 가정합니다. Task B와 Task C가 동시에 아주 빠르게 결과를 반환하면 매우 이상적이겠지만, 현실은 그렇지 않습니다. Task D가 사용될 때 Task B, Task C 둘 중 어느 값이라도 입력이 되지 않을경우 에러가 발생합니다.</p> + +<p>이러한 문제를 해결하기 위해 브라우저를 통해 특정 작업을 비동기적으로 실행할 수 있습니다. 바로 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 를 사용하는것 입니다. 아래 예시처럼 Task A가 서버에서 이미지를 가져오는 동안 Task B를 기다리게 할 수 있습니다. :</p> + +<pre class="notranslate">Main thread: Task A Task B + Promise: |__async operation__|</pre> + +<p>위의 작업은 다른 곳에서 처리가 되므로, 비동기 작업이 진행되는 동안 main thread가 차단되지 않습니다.</p> + +<p>이번 문서에서 다룬 내용은 매우 중요한 내용입니다. 다음 문서에선 비동기 코드를 어떻게 쓸 수 있는지 살펴볼 계획이므로 끝까지 읽어주시면 좋겠습니다.</p> + +<h2 id="결론">결론</h2> + +<p>현대의 소프트웨어 설계는 프로그램이 한 번에 두 가지 이상의 일을 할 수 있도록 비동기 프로그래밍을 중심으로 돌아가고 있습니다. 보다 새롭고 강력한 API를 이용하면서, 비동기로 작업해야만 하는 사례가 많아질 것입니다. 예전에는 비동기 코드를 쓰기가 힘들었습니다. 여전히 아직 어렵지만, 훨씬 쉬워졌습니다. 이 모듈의 나머지 부분에서는 비동기 코드가 왜 중요한지, 위에서 설명한 일부 문제들을 방지하는 코드 설계 방법에 대해 자세히 알아봅시다.</p> + +<h2 id="이번_module_에서는..">이번 module 에서는..</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">일반적인 비동기 프로그래밍 개념</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">비동기 자바스크립트 소개</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">협동하는 비동기 자바스크립트: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Promises와 함께하는 우아한 비동기 프로그래밍</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">비동기 프로그래밍을 쉽게 만드는 방법: async and await</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">올바른 접근 방식 선택하기</a></li> +</ul> diff --git a/files/ko/conflicting/learn/javascript/asynchronous/promises/index.html b/files/ko/conflicting/learn/javascript/asynchronous/promises/index.html new file mode 100644 index 0000000000..bbe8ee7812 --- /dev/null +++ b/files/ko/conflicting/learn/javascript/asynchronous/promises/index.html @@ -0,0 +1,414 @@ +--- +title: async와 await를 사용하여 비동기 프로그래밍을 쉽게 만들기 +slug: conflicting/Learn/JavaScript/Asynchronous/Promises +tags: + - Beginner + - CodingScripting + - Guide + - JavaScript + - Learn + - Promises + - async + - asynchronous + - await +original_slug: Learn/JavaScript/Asynchronous/Async_await +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</div> + +<p class="summary">Javascript에 대한 최신 추가 사항은 ECMAScript 2017 JavaScript 에디션의 일부인 <a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async functions</a> 그리고 <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a></code> 키워드는 ECMAScript2017에 추가되었습니다. 이 기능들은 기본적으로 비동기 코드를 쓰고 Promise를 더 읽기 더 쉽도록 만들어줍니다. 비동식 코드는 구식 동기코드를 읽기 쉽게 만들기 때문에 학습할 가치가 있습니다. 이 글은 당신이 비동기에 알아야할 것을 알려줍니다. </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">필수조건:</th> + <td>기본 컴퓨터 사용능력, 자바스크립트 기초, 비동기코드에 대한 이해</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>async/await에 대한 이해</td> + </tr> + </tbody> +</table> + +<h2 id="The_basics_of_asyncawait">async/await의 기초</h2> + +<p>async/await 코드는 두 가지 부분으로 나눠져있습니다.</p> + +<h3 id="The_async_keyword">비동기 키워드</h3> + +<p>먼저 비동기 함수를 <a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async 함수</a>로 만들기 위하여 function()앞에 <code>async</code> 키워드를 추가합니다. async function()은 <code>await</code> 키워드가 비동기 코드를 호출할 수 있게 해주는 함수 입니다.</p> + +<p>브라우저의 JavaScript 콘솔에서 아래와 같이 입력해보세요. :</p> + +<pre class="brush: js notranslate">function hello() { return "Hello" }; +hello();</pre> + +<p>위의 함수는 "Hello"를 반환합니다. — 특별할게 없죠?</p> + +<p>그러면 함수 앞에 async 키워드를 추가하면 어떻게 될까요?? 아래처럼 작성해봅시다.:</p> + +<pre class="brush: js notranslate">async function hello() { return "Hello" }; +hello();</pre> + +<p>이제 코드가 Promise를 반환합니다. 이것이 async 기능의 특징 중 하나 입니다. — 이 키워드를 사용하면 반환받는 값은 Promise가 됩니다..</p> + +<p><a href="/en-US/docs/Web/JavaScript/Reference/Operators/async_function">async function expression</a>을 사용하여 아래와 같이 만들 수도 있습니다. :</p> + +<pre class="brush: js notranslate">let hello = async function() { return "Hello" }; +hello();</pre> + +<p>화살표 함수를 사용하면 아래처럼 쓸 수 있습니다. :</p> + +<pre class="brush: js notranslate">let hello = async () => { return "Hello" };</pre> + +<p>기본적으로 두 가지는 모두 같습니다.</p> + +<p>실제로는 fulfil Promise가 반환되기 때문에 반환된 값을 사용하기 위해선 <code>.then()</code> 블럭을 사용해야 합니다. :</p> + +<pre class="brush: js notranslate">hello().then((value) => console.log(value))</pre> + +<p>짧게 표현하면 아래와 같이 쓸 수 있습니다.</p> + +<pre class="brush: js notranslate">hello().then(console.log)</pre> + +<p>이전에 봤던 내용과 비슷하죠?.</p> + +<p>정리하면, <code>async</code> 를 함수와 같이 사용하면 결과를 직접 반환하는게 아니라 Promise를 반환하게 합니다. 또한 동기식 함수는 <code>await</code>사용을 위한 지원과 함께 실행되는 잠재적인 오버헤드를 피할 수 있습니다. 함수가 <code>async</code>라고 선언될 때 필요한 핸들링만 추가하면 JavaScript엔진이 우리가 만든 프로그램을 최적화 할 수 있습니다. 끝내주죠?</p> + +<h3 id="The_await_keyword">비동기 키워드</h3> + +<p>비동기 함수를 <a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a> 키워드와 함께 쓰면 그 장점이 확실히 보입니다. 이것은 어떠한 Promise기반 함수 앞에 놓을 수 있습니다. 그리고 우리의 코드의 Promise가 fulfil될 때 까지 잠시 중단하고, 결과를 반환합니다. 그리고 실행을 기다리는 다른 코드들을 중지시키지 않고 그대로 실행되게 합니다.</p> + +<p><code>await</code> 키워드는 웹 API를 포함하여 Promise를 반환하는 함수를 호출할 때 사용할 수 있습니다.</p> + +<p>여기 간단한 예가 있습니다. :</p> + +<pre class="brush: js notranslate">async function hello() { + return greeting = await Promise.resolve("Hello"); +}; + +hello().then(alert);</pre> + +<p>물론 위의 예시는 그다지 쓸모있진 않습니다. 다만 어떻게 구문을 작성해야 하는지는 잘 나타내줍니다. 이제 실제 사례를 살펴봅시다.</p> + +<h2 id="Rewriting_promise_code_with_asyncawait">async/await와 함께 다시 쓰는 promise code</h2> + +<p>이전 문서에서 봤던 간단한 fetch() 예제를 살펴봅시다. :</p> + +<pre class="brush: js notranslate">fetch('coffee.jpg') +.then(response => response.blob()) +.then(myBlob => { + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<p>지금 시점에서 우리는 Promise가 어떻게 작동하는지 잘 이해하고 있습니다. 그렇다면 지금부터 이 예제를 async/await 를 사용하여 더 간단하게 만들어봅시다. :</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + let myBlob = await response.blob(); + + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +myFetch() +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<p>바꾸고 나니 더 이해하기 쉬워졌습니다. — 더 이상의 <code>.then()</code> 블럭은 찾아 볼 수 없습니다.</p> + +<p><code>async</code> 키워드가 함수를 Promise로 바꾸었기, 이제 promise 와 await의 하이브리드 접근방식을 사용하기 위해 코드를 리팩토링 할 수 있으며, 두 번째 <code>.then()</code>블럭을 함수 내부의 블럭으로 가져와 더 유연하게 만들 수 있습니다.</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.blob(); + +} + +myFetch().then((blob) => { + let objectURL = URL.createObjectURL(blob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}).catch(e => console.log(e));</pre> + +<p>예제를 직접 만들어보거나, 여기서 결과를 확인할 수 있습니다. <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await.html">예제</a> (여기서 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await.html">소스코드</a>를 볼 수 있습니다.).</p> + +<h3 id="But_how_does_it_work">어떻게 작동할까요?</h3> + +<p>함수 안에 코드를 작성했고, <code>function</code> 키워드 앞에 <code>async</code> 키워드를 썼다는 것을 알 수 있습니다. 꼭 이렇게 써야합니다!! 비동기 코드를 실행할 블럭을 정의하려면 비동기 함수를 생성해야 합니다. <code>await</code>는 <code>async function</code> 안에서만 쓸 수 있습니다.</p> + +<p><code>myFetch()</code> 함수 내에 코드가 이전의 Promise버전과 매우 유사하지만, 다른점이 있습니다. <code>.then()</code>블럭을 사용하여 작업을 이어가는 대신 메서드 호출 전에 <code>await</code> 키워드를 사용하여 반환된 결과를 변수에 할당합니다. <code>await</code> 키워드는 JavaScript 런타임이 이 라인에서 비동기 코드를 일시 중지하여 비동기 함수 호출이 결과를 반환할 때 까지 기다리게 합니다. 그러나 외부의 다른 동기 코드는 실행될 수 있도록 합니다. 작업이 완료되면 코드는 계속 이어져서 실행됩니다. 예를들면 아래와 같습니다. :</p> + +<pre class="brush: js notranslate">let response = await fetch('coffee.jpg');</pre> + +<p>fulfilled된 <code>fetch()</code> Promise에서 반환된 응답은 해당 응답이 사용할 수 있게 되면 <code>response</code> 변수에 할당됩니다. 그리고 parser는 해당 응답이 발생할 때 까지 이 라인에서 일시 중지됩니다. response가 사용 가능하게 되면, parser 는 다음 라인으로 이동하게 되고 그 라인에서 <code><a href="/en-US/docs/Web/API/Blob">Blob</a></code> 을 생성하게 됩니다. 이라인도 Promise기반 비동기 메서드를 호출하므로, 여기서도<code>await</code> 을 사용합니다. 비동기 작업 결과가 반환되면, <code>myFetch()</code> 함수가 그 결과를 반환합니다.</p> + +<p><code>myFetch()</code> 함수를 호출하면, Promise를 반환하므로, 따라서 화면에 Blob을 표시해주는 <code>.then()</code> 코드 블럭 체이닝 할 수 있습니다.</p> + +<p>여기까지 왔으면 이 방법이 멋있다고 생각해야합니다! 왜냐하면 <code>.then()</code> 블럭이 줄어들고 대부분이 동기 코드처럼 보이기 때문에 정말 직관적입니다.</p> + +<h3 id="Adding_error_handling">오류 처리 추가</h3> + +<p>그리고 오류 처리를 하려면 몇 가지 옵션이 있습니다.</p> + +<p>동기식 코드에서 쓰는 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code> 구문을 <code>async</code>/<code>await</code>구조에서 사용할 수 있습니다. 이 예제는 위에서 설명한 첫 번째 코드를 수정한 것 입니다. :</p> + +<pre class="brush: js notranslate">async function myFetch() { + try { + let response = await fetch('coffee.jpg'); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + let myBlob = await response.blob(); + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); + + } catch(e) { + console.log(e); + } +} + +myFetch();</pre> + +<p><code>catch() {}</code> 블록은 <code>e</code> 라고 부르는 에러 오브젝트를 통과시킵니다. 이제 콘솔에 코드가 던져준 에러 메시지를 출력할 수 있습니다.</p> + +<p>아래 코드는 처음 예제를 리팩토링한 두 번째 버전의 코드 입니다. 이 하이브리드 접근법을 사용하는 코드에서 에러를 탐지하고 싶으면 <code>.catch()</code> 블럭을 <code>.then()</code> 호출의 마지막에 작성합니다. :</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.blob(); + +} + +myFetch().then((blob) => { + let objectURL = URL.createObjectURL(blob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch((e) => + console.log(e) +);</pre> + +<p>이는 <code>.catch()</code> 블럭이 async 함수 호출과 Promise 체인 모두에서 발생하는 오류를 잡을 수 있기 때문입니다. 여기서 <code>try</code>/<code>catch</code> 블럭을 사용했더라도, <code>myFetch()</code> 에서 발생한 unhandled에러를 잡아낼 수 있습니다.</p> + +<p>위의 예제 모두를 GitHub에서 찾아볼 수 있습니다. :</p> + +<ul> + <li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">simple-fetch-async-await-try-catch.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">소스 코드</a>)</li> + <li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">simple-fetch-async-await-promise-catch.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">소스 코드</a>)</li> +</ul> + +<h2 id="Awaiting_a_Promise.all">Awaiting a Promise.all()</h2> + +<p>async/await는 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a>의 상위에 만들어져 있기 때문에 Promise의 모든 기능을 사용할 수 있습니다. <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all()</a></code> 을 포함해서 말이죠 — 아래 보이는 코드처럼 <code>Promise.all()</code> 앞에 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">async</span></font>키워드를 사용하여 동기식 코드처럼 작성할 수 있습니다. 이전 문서를 확인해봅시다. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">이전에 글에서 본 예제</a>. 새로운 버전과 비교하기 위해 탭을 분리 해보세요.</p> + +<p>async/await 스타일로 변경한 코드는 아래와 같습니다. (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-all-async-await.html">데모</a> 그리고 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-all-async-await.html">소스 코드</a>) :</p> + +<pre class="brush: js notranslate">async function fetchAndDecode(url, type) { + let response = await fetch(url); + + let content; + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } else { + if(type === 'blob') { + content = await response.blob(); + } else if(type === 'text') { + content = await response.text(); + } + } + + return content; + + +} + +async function displayContent() { + let coffee = fetchAndDecode('coffee.jpg', 'blob'); + let tea = fetchAndDecode('tea.jpg', 'blob'); + let description = fetchAndDecode('description.txt', 'text'); + + let values = await Promise.all([coffee, tea, description]); + + let objectURL1 = URL.createObjectURL(values[0]); + let objectURL2 = URL.createObjectURL(values[1]); + let descText = values[2]; + + let image1 = document.createElement('img'); + let image2 = document.createElement('img'); + image1.src = objectURL1; + image2.src = objectURL2; + document.body.appendChild(image1); + document.body.appendChild(image2); + + let para = document.createElement('p'); + para.textContent = descText; + document.body.appendChild(para); +} + +displayContent() +.catch((e) => + console.log(e) +);</pre> + +<p>몇 가지 사항을 조금 수정했을 뿐인데 <code>fetchAndDecode()</code>함수를 쉽게 비동기 함수로 변환했습니다. <code>Promise.all()</code> 라인을 살펴보세요:</p> + +<pre class="brush: js notranslate">let values = await Promise.all([coffee, tea, description]);</pre> + +<p>여기에 <code>await</code> 을 사용하여 세 가지 Promise의 결과가 반환되었을 때 <code>values</code> 배열에 담을 수 있습니다. 그리고 마치 동기화 코드처럼 보이죠. 우리가 작업한건 <code>displayContent()</code>에 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">async</span></font>키워드를 추가하고, 모든 코드를<code>.then()</code> 블럭 바깥으로 빼냈습니다. 또한 아주 적은양의 코드 수정도 했죠. 이렇게 하니 더 단순하고, 유용하고 읽기 쉬운 프로그램이 되었습니다.</p> + +<p>마지막으로 에러를 다루기 위해 <code>.catch()</code> 블럭을 <code>displayContent()</code> 함수를 호출하는 곳에 추가했습니다. 이렇게 하면 두 함수에서 발생하는 에러를 처리할 수 있습니다.</p> + +<div class="blockIndicator note"> + <p><strong>Note</strong>: <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#the_finally_clause">finally</a></code>비동기 블록 대신 비동기 함수 내에서 <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally">.finally()</a></code> 동기 블록을 사용하여 작업이 어떻게 진행되었는지에 대한 최종 보고서를 표시할 수 있습니다. <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-finally-async-await.html">예제</a>에서도 확인이 가능합니다. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-finally-async-await.html">소스 코드</a>).</p> +</div> + +<h2 id="The_downsides_of_asyncawait">async/await의 단점</h2> + +<p>앞서 봤듯이 async/await은 매우 유용하지만 고려해야 할 몇 가지 단점이 있습니다.</p> + +<p>Async/await 는 우리의 코드를 마치 동기식 코드처럼 보이게 합니다. 그리고 어떤 면에서는 정말로 동기적으로 행동합니다. 함수 블럭에 여러 개의 <code>await</code> 키워드를 사용하면 Promise가 fulfilled되기 전 까지 다음 <code>await</code> 을 차단합니다. 그 동안 다른 태스크는 계속 실행이 되지만 정의한 함수 내에서는 동기적으로 작동할 것 입니다.</p> + +<p>이 말은 우리가 작성한 코드가 바로 이어지는 수 많은 Promise에 의해 느려질 수 있다는 것을 의미합니다. 각 <code>await</code> 는 이전의 작업이 끝날 때 까지 기다립니다(Promise 체이닝과 혼동하지 마세요). 그런데 우리가 원하는건 기다리는게 아니고 일제히 실행되는 것 입니다.</p> + +<p>이 문제를 완화할 수 있는 패턴이 있습니다. — 모든 <code>Promise</code> 오브젝트를 변수에 저장하여 미리 실행되게 하고 변수가 사용 가능할 때 꺼내서 쓰는 것 입니다. 어떻게 작동하는지 한번 살펴봅시다.</p> + +<p>두 가지 예시를 보여 드리겠습니다. — 느린 비동기 작업 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/slow-async-await.html">slow-async-await.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/slow-async-await.html">소스 코드</a>) 그리고 빠른 비동기 작업 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/fast-async-await.html">fast-async-await.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/fast-async-await.html">소스 코드</a>)입니다. 두 예제에서 마치 비동기 작업인 것 처럼 보이기 위해 <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code> 을 사용했습니다. :</p> + +<pre class="brush: js notranslate">async function makeResult(items) { + let newArr = []; + for(let i=0; i < items.length; i++) { + newArr.push('word_'+i); + } + return newArr; + } + + async function getResult() { + let result = await makeResult(items); // Blocked on this line + useThatResult(result); // Will not be executed before makeResult() is done + } + </pre> + +<p>그리고 세 가지 <code>timeoutPromise()</code> 함수를 호출하는 <code>timeTest()</code>함수를 만들었습니다.</p> + +<pre class="brush: js notranslate">async function timeTest() { + ... +}</pre> + +<p>그리고 두 개 예제 모두 시작 시간을 기록하고, <code>timeTest()</code> Promise가 fulfilled된 시간을 저장하여 두 시간의 차를 계산해 작업이 얼마나 걸렸는지 사용자에게 보여줍니다. :</p> + +<pre class="brush: js notranslate">let startTime = Date.now(); +timeTest().then(() => { + let finishTime = Date.now(); + let timeTaken = finishTime - startTime; + alert("Time taken in milliseconds: " + timeTaken); +})</pre> + +<p><code>timeTest()</code> 함수만 두 예제에서 차이가 있습니다.</p> + +<p><code>slow-async-await.html</code> 예제이서, <code>timeTest()</code> 함수는 아래와 같이 생겼습니다. :</p> + +<pre class="brush: js notranslate">async function timeTest() { + await timeoutPromise(3000); + await timeoutPromise(3000); + await timeoutPromise(3000); +}</pre> + +<p>아주 간단하게 <code>timeoutPromise()</code> 함수를 직접 호출했습니다. 각 작업은 3초씩 걸립니다. 그리고 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 키워드를 사용했기 때문에 이전 await 작업이 끝나야 다음으로 진행됩니다. — 첫 번째 예제를 실행하면, alert 박스에서 약 9초(9000밀리초)가 걸렸음을 확인할 수 있습니다.</p> + +<p>다음으로 <code>fast-async-await.html</code> 예제이서, <code>timeTest()</code> 은 아래와 같이 생겼습니다. :</p> + +<pre class="brush: js notranslate">async function timeTest() { + const timeoutPromise1 = timeoutPromise(3000); + const timeoutPromise2 = timeoutPromise(3000); + const timeoutPromise3 = timeoutPromise(3000); + + await timeoutPromise1; + await timeoutPromise2; + await timeoutPromise3; +}</pre> + +<p>여기선 세 가지 <code>Promise</code> 오브젝트를 변수에 저장하여 동시에 작업을 시작하도록 했습니다.</p> + +<p>그리고 그 변수에 await을 사용하여 결과를 호출합니다. — 작업이 거의 동시에 시작됐기 때문에, Promise도 거의 동시에 fulfilled될 것 입니다. 두 번째 예제를 실행하면 거의 3초(3000밀리초) 만에 작업이 끝났음을 확인할 수 있습니다.</p> + +<p>코드를 주의깊게 테스트 하고, 성능이 떨어지기 시작하면 위의 상황을 의심해봐야 합니다.</p> + +<p>다른 아주 사소한 단점은 비동기로 실행될 Promise가 있다면 async함수 안에 항상 await을 써야한다는 것 입니다.</p> + +<h2 id="Asyncawait_class_methods">Async/await class 메서드</h2> + +<p>마지막으로 보여줄 내용은 <code>async</code> 키워드를 class/object의 메서드에 사용하여 Promise를 반환하게 만들 수 있다는 것 입니다. 그리고 <code>await</code> 를 그 안에 넣을 수도 있습니다. 다음 문서를 살펴보세요 > <a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance#ecmascript_2015_classes">ES class code we saw in our object-oriented JavaScript article</a>, 그리고 보이는 코드를 <code>async</code> 메서드로 수정한 아래의 내용과 비교 해보세요 :</p> + +<pre class="brush: js notranslate">class Person { + constructor(first, last, age, gender, interests) { + this.name = { + first, + last + }; + this.age = age; + this.gender = gender; + this.interests = interests; + } + + async greeting() { + return await Promise.resolve(`Hi! I'm ${this.name.first}`); + }; + + farewell() { + console.log(`${this.name.first} has left the building. Bye for now!`); + }; +} + +let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);</pre> + +<p>이제 클래스의 첫 번째 메서드를 아래와 같이 사용할 수 있습니다. :</p> + +<pre class="brush: js notranslate">han.greeting().then(console.log);</pre> + +<h2 id="Browser_support">브라우저 지원</h2> + +<p>async/await 사용 여부를 결정할 때 고려해야 할 한가지 사항은 이전 브라우저에 대한 지원입니다. + promises와 마찬가지로 대부분의 최신 브라우저에서 사용할 수 있습니다. + 주요 지원 문제는 Internet Explorer 그리고 Opera Mini에서 발생합니다.</p> + +<p>async/await을 사용하는데 브라우저 지원이 걱정되는 경우 <a href="https://babeljs.io/">BabelJS</a> 라이브러리를 사용하는 것을 고려해 볼 수 있습니다. BabelJS는 최신 자바스크립트를 사용하여 애플리케이션을 작성하고 사용자 브라우저에 필요한 변경사항을 Babel이 파악할 수 있도록 지원합니다. async/await를 지원하지 않는 브라우저를 만나면 Babel은 이전 브라우저에서 작동하는 polyfill를 자동으로 제공합니다.</p> + +<h2 id="Conclusion">결론</h2> + +<p>async/await를 사용하면 읽기 쉽고 유지보수가 편리한 비동기 코드를 간단하게 작성할 수 있습니다. 브라우저 지원이 다른 비동기 코드에 비해 제한적이기는 하지만 현재는 물론 미래에도 사용을 위해 배울 가치는 충분합니다.</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="In_this_module">이 모듈에서</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li> + </ul> |