From d53bae971e455859229bcb3246e29bcbc85179d2 Mon Sep 17 00:00:00 2001 From: MDN Date: Tue, 8 Mar 2022 00:12:08 +0000 Subject: [CRON] sync translated content --- files/ko/_redirects.txt | 3 + files/ko/_wikihistory.json | 44 +- .../learn/javascript/asynchronous/index.html | 599 +++++++++++++++++++++ .../javascript/asynchronous/introducing/index.html | 160 ++++++ .../javascript/asynchronous/promises/index.html | 414 ++++++++++++++ .../javascript/asynchronous/async_await/index.html | 413 -------------- .../javascript/asynchronous/concepts/index.html | 159 ------ .../asynchronous/timeouts_and_intervals/index.html | 598 -------------------- 8 files changed, 1198 insertions(+), 1192 deletions(-) create mode 100644 files/ko/conflicting/learn/javascript/asynchronous/index.html create mode 100644 files/ko/conflicting/learn/javascript/asynchronous/introducing/index.html create mode 100644 files/ko/conflicting/learn/javascript/asynchronous/promises/index.html delete mode 100644 files/ko/learn/javascript/asynchronous/async_await/index.html delete mode 100644 files/ko/learn/javascript/asynchronous/concepts/index.html delete mode 100644 files/ko/learn/javascript/asynchronous/timeouts_and_intervals/index.html (limited to 'files/ko') diff --git a/files/ko/_redirects.txt b/files/ko/_redirects.txt index dec0d5b016..f8ecb5e8f5 100644 --- a/files/ko/_redirects.txt +++ b/files/ko/_redirects.txt @@ -313,6 +313,9 @@ /ko/docs/Learn/HTML/Forms/Your_first_HTML_form /ko/docs/Learn/Forms/Your_first_form /ko/docs/Learn/HTML/Howto/데이터_속성_사용하기 /ko/docs/Learn/HTML/Howto/Use_data_attributes /ko/docs/Learn/HTML/Multimedia_and_embedding/ideo_and_audio_content /ko/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content +/ko/docs/Learn/JavaScript/Asynchronous/Async_await /ko/docs/conflicting/Learn/JavaScript/Asynchronous/Promises +/ko/docs/Learn/JavaScript/Asynchronous/Concepts /ko/docs/conflicting/Learn/JavaScript/Asynchronous/Introducing +/ko/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals /ko/docs/conflicting/Learn/JavaScript/Asynchronous /ko/docs/Learn/JavaScript/Building_blocks/조건문 /ko/docs/Learn/JavaScript/Building_blocks/conditionals /ko/docs/Learn/JavaScript/Objects/Inheritance /ko/docs/Learn/JavaScript/Objects/Classes_in_JavaScript /ko/docs/Learn/JavaScript/Objects/Object-oriented_JS /ko/docs/conflicting/Learn/JavaScript/Objects/Classes_in_JavaScript diff --git a/files/ko/_wikihistory.json b/files/ko/_wikihistory.json index b4a8b6a3ff..ecc1af296f 100644 --- a/files/ko/_wikihistory.json +++ b/files/ko/_wikihistory.json @@ -1888,22 +1888,6 @@ "Sheppy" ] }, - "Learn/JavaScript/Asynchronous/Async_await": { - "modified": "2020-11-28T09:53:12.946Z", - "contributors": [ - "HerbertLim", - "chupark" - ] - }, - "Learn/JavaScript/Asynchronous/Concepts": { - "modified": "2020-11-17T21:41:39.368Z", - "contributors": [ - "mochapoke", - "Plut0", - "chupark", - "eunjungleecub" - ] - }, "Learn/JavaScript/Asynchronous/Introducing": { "modified": "2020-11-17T21:53:14.519Z", "contributors": [ @@ -1918,12 +1902,6 @@ "chupark" ] }, - "Learn/JavaScript/Asynchronous/Timeouts_and_intervals": { - "modified": "2020-07-16T22:33:21.049Z", - "contributors": [ - "cjkcc69" - ] - }, "Learn/JavaScript/Building_blocks": { "modified": "2020-07-16T22:31:09.649Z", "contributors": [ @@ -17457,6 +17435,28 @@ "Kaben" ] }, + "conflicting/Learn/JavaScript/Asynchronous": { + "modified": "2020-07-16T22:33:21.049Z", + "contributors": [ + "cjkcc69" + ] + }, + "conflicting/Learn/JavaScript/Asynchronous/Introducing": { + "modified": "2020-11-17T21:41:39.368Z", + "contributors": [ + "mochapoke", + "Plut0", + "chupark", + "eunjungleecub" + ] + }, + "conflicting/Learn/JavaScript/Asynchronous/Promises": { + "modified": "2020-11-28T09:53:12.946Z", + "contributors": [ + "HerbertLim", + "chupark" + ] + }, "conflicting/Learn/JavaScript/Objects": { "modified": "2019-03-23T23:29:46.029Z", "contributors": [ 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 +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
+ +

이 장에서는 자바스크립트가 설정된 시간이 경과하거나 혹은 정해진 시간 간격(예 : 초당 설정된 횟수)으로 비동기 코드를 작동하는 전형적인 방법을 살펴본다. 그리고 이 방법들이 어떤 것에 유용한지 얘기해 보고, 그 본질적인 문제에 대해 살펴본다.

+ + + + + + + + + + + + +
Prerequisites:Basic computer literacy, a reasonable understanding of JavaScript fundamentals.
Objective:To understand asynchronous loops and intervals and what they are useful for.
+ +

Introduction

+ +

오랜 시간 동안 웹플랫폼은 자바스크립트 프로그래머가 일정한 시간이 흐른 뒤에 비동기적 코드를 실행할 수 있게하는 다양한 함수들을 제공해 왔다. 그리고 프로그래머가 중지시킬 때까지 코드 블록을 반복적으로 실행하기 위한 다음과 같은 함수들이 있다.

+ +
+
setTimeout()
+
특정 시간이 경과한 뒤에 특정 코드블록을 한번 실행한다.
+
+
setInterval()
+
각각의 호출 간에 일정한 시간 간격으로 특정 코드블록을 반복적으로 실행한다.
+
requestAnimationFrame()
+
setInterval()의 최신 버전; 그 코드가 실행되는 환경에 관계없이 적절한 프레임 속도로 애니메이션을 실행시키면서, 브라우저가 다음 화면을 보여주기 전에 특정 코드블록을 실행한다.
+
+ +

이 함수들에 의해 설정된 비동기 코드는 메인 스레드에서 작동한다. 그러나 프로세서가 이러한 작업을 얼마나 많이 수행하는지에 따라, 코드가 반복되는 중간에 다른 코드를 어느 정도 효율적으로 실행할 수 있다. 어쨌든 이러한 함수들은 웹사이트나 응용 프로그램에서 일정한 애니메이션 및 기타 배경 처리를 실행하는 데 사용된다. 다음 섹션에서는 그것들을 어떻게 사용할 수 있는지 보여줄 것이다.

+ +

setTimeout()

+ +

앞에서 언급했듯이 setTimeout ()은 지정된 시간이 경과 한 후 특정 코드 블록을 한 번 실행한다. 그리고 다음과 같은 파라미터가 필요하다.:

+ + + +
+

Note: 타임아웃 콜백은 단독으로 실행되지 않기 때문에 지정된 시간이 지난 그 시점에 정확히  콜백 될 것이라는 보장은 없다. 그보다는 최소한 그 정도의 시간이 지난 후에 호출된다. 메인 스레드가 실행해야 할 핸들러를 찾기 위해 이런 핸들러들을 살펴보는 시점에 도달할 때까지 타임아웃 핸들러를 실행할 수 없다.

+
+ +

아래 예제에서 브라우저는 2초가 지나면 익명의 함수를 실행하고 경보 메시지를 띄울 것이다. (see it running live, and see the source code):

+ +
let myGreeting = setTimeout(function() {
+  alert('Hello, Mr. Universe!');
+}, 2000)
+ +

지정한 함수가 꼭 익명일 필요는 없다. 함수에 이름을 부여 할 수 있고, 다른 곳에서 함수를 정의하고 setTimeout ()에 참조(reference)를 전달할 수도 있다. 아래 코드는 위의 코드와 같은 실행 결과를 얻을 수 있다. 

+ +
// 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);
+ +

예를 들자면 timeout 함수와 이벤트에 의해 중복 호출되는 함수를 사용하려면 이 방법이 유용할 수 있다. 이 방법은 코드라인을 깔끔하게  정리하는 데 도움을 준다. 특히 timeout 콜백의 코드라인이 여러 줄이라면 더욱 그렇다.  

+ +

setTimeout ()은 나중에 타임아웃을 할 경우에 타임아웃을 참조하는데 사용하는 식별자 값을 리턴한다.  그 방법을 알아 보려면 아래{{anch("Clearing timeouts")}}을 참조하세요.

+ +

setTimeout () 함수에 매개변수(parameter) 전달

+ +

setTimeout () 내에서 실행되는 함수에 전달하려는 모든 매개변수는 setTimeout () 매개변수 목록 끝에 추가하여 전달해야 한다. 아래 예제처럼, 이전 함수를 리팩터링하여 전달된 매개변수의 사람 이름이 추가된 문장을 표시할 수 있다.

+ +
function sayHi(who) {
+  alert('Hello ' + who + '!');
+}
+ +

Say hello의 대상이 되는 사람이름은 setTimeout()의 세번째 매개변수로 함수에 전달된다.

+ +
let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');
+ +

timeout 취소

+ +

마지막으로 타임아웃이 생성되면(setTimeout()이 실행되면) 특정시간이 경과하기 전에 clearTimeout()을 호출하여 타임아웃을 취소할 수 있다.  clearTimeout()은  setTimeout()콜의 식별자를 매개변수로  setTimeout()에 전달한다. 위 예제의 타임아웃을 취소하려면 아래와 같이 하면 된다.

+ +
clearTimeout(myGreeting);
+ +
+

Note: 인사를 할 사람의 이름을 설정하고 별도의 버튼을 사용하여 인사말을 취소 할 수있는 약간 더 복잡한 폼양식 예제인  greeter-app.html 을 참조하세요.

+
+ +

setInterval()

+ +

setTimeout ()은 일정 시간이 지난 후 코드를 한 번 실행해야 할 때 완벽하게 작동합니다. 그러나 애니메이션의 경우와 같이 코드를 반복해서 실행해야 할 경우 어떨까요?

+ +

이럴 경우에 setInterval ()이 필요합니다. setInterval ()은 setTimeout ()과 매우 유사한 방식으로 작동합니다. 다만 setTimeout ()처럼 첫 번째 매개 변수(함수)가 타임아웃 후에 한번 실행되는게 아니라 두 번째 매개 변수에 주어진 시간까지 반복적으로 실행되는 것이 차이점입니다.  setInterval() 호출의 후속 파라미터로 실행 중인 함수에 필요한 파라미터를 전달할 수도 있다.

+ +

예를 들어 봅시다. 다음 함수는 새 Date() 객체를 생성한 후에 ToLocaleTimeString()을 사용하여 시간데이터를 문자열로 추출한 다음 UI에 표시합니다. 그리고 setInterval()을 사용하여 초당 한 번 함수(displayTime)를 실행하면 초당 한 번 업데이트되는 디지털 시계와 같은 효과를 만들어냅니다.(see this live, and also see the source): 

+ +
function displayTime() {
+   let date = new Date();
+   let time = date.toLocaleTimeString();
+   document.getElementById('demo').textContent = time;
+}
+
+const createClock = setInterval(displayTime, 1000);
+ +

setTimeout()과 같이 setInterval()도 식별자 값을 리턴하여 나중에 interval을 취소해야 할 때 사용한다.

+ +

interval 취소

+ +

setInterval ()은 아무 조치를 취하지 않으면 끊임없이 계속 실행됩니다. 이 상태를 중지하는 방법이 필요합니다. 그렇지 않으면 브라우저가 추가 작업을 완료 할 수 없거나, 현재 처리 중인 애니메이션이 완료되었을 때 오류가 발생할 수 있습니다. setTimeout()과 같은 방식으로 setInterval () 호출에 의해 반환 된 식별자를 clearInterval () 함수에 전달하여 이 작업을 취소할수 있습니다.

+ +
const myInterval = setInterval(myFunction, 2000);
+
+clearInterval(myInterval);
+ +

능동학습 : 자신만의 스톱워치를 만들기.

+ +

위에서 모두 설명해 드렸으니, 과제를 드리겠습니다. setInterval-clock.html 예제를 수정하여 자신만의 간단한 스톱워치를 만들어 보세요.

+ +

위에서 설명(디지털 시계)한 것처럼 시간을 표시해야 하고, 이 예제에서는 아래 기능을 추가하세요.

+ + + +

몇가지 힌트를 드립니다.

+ + + +
+

Note: If you get stuck, you can find our version here (see the source code also).

+
+ +

setTimeout()과 setInterval()에서 주의해야할 것 들

+ +

setTimeout()과 setInterval()에는 몇가지 주의해야 할 것들이 있습니다. 어떤 것인지 한번 살펴보겠습니다.

+ +

순환 timeouts

+ +

setTimeout()을 사용하는 또 다른 방법입니다. 바로 setInterval()을 사용하는 대신 setTimeout()을 이용해 같은코드를 반복적으로 실행시키는 방법입니다.

+ +

The below example uses a recursive setTimeout() to run the passed function every 100 milliseconds: 아래 예제에서는 setTimeout()이 주어진 함수를 100밀리세컨드마다 실행합니다.

+ +
let i = 1;
+
+setTimeout(function run() {
+  console.log(i);
+  i++;
+  setTimeout(run, 100);
+}, 100);
+ +

위 예제를 아래 예제와 비교해 보세요. 아래 예제는 setInterval()을 사용하여 같은 결과를 얻을 수 있습니다.

+ +
let i = 1;
+
+setInterval(function run() {
+  console.log(i);
+  i++
+}, 100);
+ +

그렇다면 순환 setTimeout()과 setInterval()은 어떻게 다를까요?

+ +

두 방법의 차이는 미묘합니다.

+ + + +

코드가 지정한 시간 간격보다 실행 시간이 오래 걸리면 순환 setTimeout ()을 사용하는 것이 좋습니다. 이렇게하면 코드 실행 시간에 관계없이 실행 간격이 일정하게 유지되어 오류가 발생하지 않습니다.

+ +

즉시 timeouts

+ +

setTimeout()의 값으로 0을 사용하면 메인 코드 스레드가 실행된 후에 가능한 한 빨리 지정된 콜백 함수의 실행을 예약할 수 있다.

+ +

예를 들어 아래 코드 (see it live) 는 "Hello"가 포함된 alert를 출력 한 다음 첫 번째 경고에서 OK를 클릭하자마자 "World"가 포함된 alert를 출력합니다.

+ +
setTimeout(function() {
+  alert('World');
+}, 0);
+
+alert('Hello');
+ +

이것은 모든 메인 스레드의 실행이 완료되자마자 실행되도록 코드 블록을 설정하려는 경우 유용할 할 수 있습니다. 비동기 이벤트 루프에 배치하면 곧바로 실행될 겁니다.

+ +

clearTimeout() 와 clearInterval()의 취소기능

+ +

clearTimeout ()과 clearInterval ()은 모두 동일한 entry를 사용하여 대상 메소드(setTimeout () 또는 setInterval ())을 취소합니다. 흥미롭게도 이는 setTimeout () 또는 setInterval ()을 지우는 데 clearTimeout ()과 clearInterval ()메소드 어느 것을 사용해도 무방합니다.

+ +

그러나 일관성을 유지하려면 clearTimeout ()을 사용하여 setTimeout () 항목을 지우고 clearInterval ()을 사용하여 setInterval () 항목을 지우십시오. 혼란을 피하는 데 도움이됩니다.

+ +

requestAnimationFrame()

+ +

requestAnimationFrame ()은 브라우저에서 애니메이션을 효율적으로 실행하기 위해 만들어진 특수한 반복 함수입니다. 근본적으로 setInterval ()의 최신 버전입니다. 브라우저가 다음에 디스플레이를 다시 표시하기 전에 지정된 코드 블록을 실행하여 애니메이션이 실행되는 환경에 관계없이 적절한 프레임 속도로 실행될 수 있도록합니다.

+ +

setInterval ()을 사용함에 있어 알려진 문제점을 개선하기위해 만들어졌습니다. 예를 들어 장치에 최적화 된 프레임 속도로 실행되지 않는 문제,  때로는 프레임을 빠뜨리는 문제,  탭이 활성 탭이 아니거나 애니메이션이 페이지를 벗어난 경우에도 계속 실행되는 문제 등등이다 . CreativeJS에서 이에 대해 자세히 알아보십시오.

+ +
+

Note: requestAnimationFrame() 사용에 관한 예제들은 이 코스의 여러곳에서 찾아볼 수 있습니다. Drawing graphics 와 Object building practice 의 예제를 찾아 보세요.

+
+ +

이 메소드는 화면을 다시 표시하기 전에 호출 할 콜백을 인수로 사용합니다. 이것이 일반적인 패턴입니다. 아래는 사용예를 보여줍니다.

+ +
function draw() {
+   // Drawing code goes here
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

그 발상은 애니메이션을 업데이트하는 함수 (예 : 스프라이트 이동, 스코어 업데이트, 데이터 새로 고침 등)를 정의한 후 그것을 호출하여 프로세스를 시작하는 것입니다. 함수 블록의 끝에서 매개 변수로 전달 된 함수 참조를 사용하여 requestAnimationFrame ()을 호출하면 브라우저가 다음 화면을 재표시할 때 함수를 다시 호출하도록 지시합니다. 그런 다음 requestAnimationFrame ()을 반복적으로 호출하므로 계속 실행되는 것입니다.

+ +
+

Note: 어떤 간단한  DOM 애니메이션을 수행하려는 경우,   CSS Animations 은 JavaScript가 아닌 브라우저의 내부 코드로 직접 계산되므로 속도가 더 빠릅니다. 그러나 더 복잡한 작업을 수행하고 DOM 내에서 직접 액세스 할 수 없는 객체(예 :2D Canvas API or WebGL objects)를 포함하는 경우 대부분의 경우 requestAnimationFrame ()이 더 나은 옵션입니다. 

+
+ +

여러분의 애니메이션의 작동속도는 얼마나 빠른가요?

+ +

부드러운 애니메이션을 구현은 직접적으로 프레임 속도에 달려 있으며, 프레임속도는 초당 프레임 (fps)으로 측정됩니다. 이 숫자가 높을수록 애니메이션이 더 매끄럽게 보입니다.

+ +

일반적으로 화면 재생률는 60Hz이므로 웹 브라우저를 사용할 때 여러분이 설정할 수 있는 가장 빠른 프레임 속도는 초당 60 프레임 (FPS)입니다. 이 속도보다 빠르게 설정하면 과도한 연산이 실행되어 화면이 더듬거리고 띄엄띄엄 표시될 수 있다. 이런 현상을 프레임 손실 또는 쟁크라고 한다. 

+ +

재생률 60Hz의 모니터에 60FPS를 달성하려는 경우 각 프레임을 렌더링하기 위해 애니메이션 코드를 실행하려면 약 16.7ms(1000/60)가 필요합니다. 그러므로 각 애니메이션 루프를 통과 할 때마다 실행하려고 하는 코드의 양을 염두에 두어야합니다.

+ +

requestAnimationFrame()은 불가능한 경우에도 가능한한 60FPS 값에 가까워 지려고 노력합니다. 실제로 복잡한 애니메이션을 느린 컴퓨터에서 실행하는 경우 프레임 속도가 떨어집니다. requestAnimationFrame ()은 항상 사용 가능한 것을 최대한 활용합니다.

+ +

requestAnimationFrame()이 setInterval(), setTimeout()과 다른점은?

+ +

requestAnimationFrame () 메소드가 이전에 살펴본 다른 메소드와 어떻게 다른지에 대해 조금 더 이야기하겠습니다. 위의 코드를 다시 살펴보면;

+ +
function draw() {
+   // Drawing code goes here
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

setInterval()을 사용하여 위와 같은 작업을 하는 방법을 살펴봅시다.

+ +
function draw() {
+   // Drawing code goes here
+}
+
+setInterval(draw, 17);
+ +

앞에서언급했듯이 requestAnimationFrame ()은 시간 간격을 지정하지 않습니다. requestAnimationFrame ()은 현재 상황에서 최대한 빠르고 원활하게 실행됩니다. 어떤 이유로 애니메이션이 화면에 표시되지 않으면 브라우저는 그 애니메이션을 실행하는 데 시간을 낭비하지 않습니다.

+ +

반면에 setInterval ()은 특정 시간간격을 필요로 합니다. 1000 ms/60Hz 계산을 통해 최종값 16.6에 도달 한 후 반올림(17)했습니다. 이때 반올림하는 것이 좋습니다. 그 이유는 반내림(16)을하면 60fps보다 빠르게 애니메이션을 실행하려고 하게 되지만 애니메이션의 부드러움에 아무런 영향을 미치지 않기 때문입니다. 앞에서 언급했듯이 60Hz가 표준 재생률입니다.

+ +

timestamp를 포함하기

+ +

requestAnimationFrame() 함수에 전달 된 실제 콜백에는 requestAnimationFrame()이 실행되기 시작한 이후의 시간을 나타내는 timestamp를  매개변수로 제공할 수 있습니다. 장치 속도에 관계없이 특정 시간과 일정한 속도로 작업을 수행할 수 있으므로 유용합니다. 사용하는 일반적인 패턴은 다음과 같습니다.

+ +
let startTime = null;
+
+function draw(timestamp) {
+    if(!startTime) {
+      startTime = timestamp;
+    }
+
+   currentTime = timestamp - startTime;
+
+   // Do something based on current time
+
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

브라우저 지원

+ +

requestAnimationFrame()은 setInterval() / setTimeout()보다 좀 더 최신 브라우저에서 지원됩니다. 가장 흥미롭게도 Internet Explorer 10 이상에서 사용할 수 있습니다. 따라서 별도의 코드로 이전 버전의 IE를 지원해야할 필요가 없다면, requestAnimationFrame()을 사용하지 않을 이유가 없습니다.

+ +

간단한 예

+ +

지금까지 이론적으로는 충분히 살펴보았습니다. 그러면 직접 requestAnimationFrame() 예제를 작성해 봅시다. 우리는 간단한 "스피너 애니메이션"을 만들 것입니다. 여러분들이 앱 사용중 서버 과부하일 때 이것을 자주 보았을 겁니다.

+ +
+

Note: 실제로는 CSS 애니메이션을 사용하여 이러한 종류의 간단한 애니메이션을 실행해야 합니다. 그러나 이러한 종류의 예제는 requestAnimationFrame() 사용법을 보여주는 데 매우 유용하며, 각 프레임에서 게임의 디스플레이를 업데이트하는 것과 같이 좀 더 복잡한 작업을 수행 할 때 이러한 종류의 기술을 사용하는 것이 좋습니다.

+
+ +
    +
  1. +

    먼저 HTML 템플릿을 여기에서.가져옵니다.

    +
  2. +
  3. +

    <body>안에 빈 <div> 요소를 삽입합니다. 그리고  ↻캐릭터를 그 안에 추가합니다. 이 예제에서 이 원형 화살표가 회전하게됩니다.

    +
  4. +
  5. +

    아래 CSS를 HTML 템플릿에 여러분이 원하는 방식으로 적용하세요. 이 CSS는 페이지 배경을 빨간색으로, <body>의 height를 html height의 100%로 설정합니다. 그리고 <div>를 수직, 수평으로 <body> 중앙에 위치 시킵니다.

    + +
    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;
    +}
    +
  6. +
  7. +

    </body> 태크 위에 <script>를 추가하세요.

    +
  8. +
  9. +

    <script> 안에 아래 자바스크립트 코드를 추가하세요. 여기에서 <div>의 참조를 상수로 저장하고, rotateCount 변수를 0으로 설정하고, 나중에 requestAnimationFrame()의 시작 시간을 저장할 startTime 변수를 null로 설정하고, 그리고 requestAnimationFrame() 콜의 참조를 저장할 초기화하지 않은 rAF 변수를 선언합니다.

    + +
    const spinner = document.querySelector('div');
    +let rotateCount = 0;
    +let startTime = null;
    +let rAF;
    +
    +
  10. +
  11. +

    그 아래에 draw() 함수를 추가합니다. 이 함수는 timestamp 매개변수를 포함하는 애니메이션 코드 작성에 사용됩니다.

    + +
    function draw(timestamp) {
    +
    +}
    +
  12. +
  13. +

    draw() 함수안데 다음 코드를 추가합니다. if 조건문으로 startTime이 정의되지 않았다면 startTime을 정의합니다 (루프 반복의 첫번째에만 작동합니다).  그리고 스피너를 회전시키는 rotateCount 변수값을 설정합니다(현재 timestamp는 시작 timestamp를 3으로 나눈 것이라서 그리 빠르지 않습니다).

    + +
      if (!startTime) {
    +   startTime = timestamp;
    +  }
    +
    +  rotateCount = (timestamp - startTime) / 3;
    +
    +
  14. +
  15. +

    draw() 함수 안의 이전 코드 아래에 다음 블록을 추가합니다. 이렇게하면 rotateCount 값이 359보다 큰지 확인합니다 (예 : 360, 완전한 원). 그렇다면 값을 모듈러 360 (즉, 값을 360으로 나눌 때 남은 나머지)으로 설정하여 원 애니메이션이 합리적인 낮은 값으로 중단없이 계속 될 수 있습니다. 꼭 이렇게 해야되는 것은 아니지만 "128000도" 같은 값보다는 0~359 도의 값으로 작업하는 것이 더 쉽습니다.

    + +
    if (rotateCount > 359) {
    +  rotateCount %= 360;
    +}
    +
  16. +
  17. 다음으로 아래 코드를 추가하세요. 실제 스피너를 회전시키는 코드입니다. +
    spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    +
  18. +
  19. +

     draw() 함수 제일 아래에 다음 코드를 추가합니다. 이 코드는 모든 작업의 키 포인트입니다. draw() 함수를 매개변수로 가져오는 requestAnimationFrame() 콜을 저장하기 위해 앞에서 정의한  rAF 변수를 설정합니다. 이 코드는 애니메이션을 실행시키고, 가능한한 60fps에 근사하게 draw() 함수를 계속 실행합니다.

    + +
    rAF = requestAnimationFrame(draw);
    +
  20. +
+ +
+

Note: You can find this example live on GitHub (see the source code also).

+
+ +

requestAnimationFrame() call의 취소

+ +

cancelAnimationFrame() 메소드를 호출하여 requestAnimationFrame()을 취소할 수 있다. (접두어가 "clear"가 아니고 "cancel"임에 유의) requestAnimationFrame()에 의해서 리턴된 rAF 변수값을 전달받아 취소한다. 

+ +
cancelAnimationFrame(rAF);
+ +

Active learning: Starting and stopping our spinner

+ +

In this exercise, we'd like you to test out the cancelAnimationFrame() 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.

+ +

Some hints:

+ + + +
+

Note: Try this yourself first; if you get really stuck, check out of our live example and source code.

+
+ +

Throttling a requestAnimationFrame() animation

+ +

One limitation of requestAnimationFrame() 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?

+ +

This was a problem for example in the Monkey Island-inspired walking animation from our Drawing Graphics article:

+ +

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

+ +

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 requestAnimationFrame(), 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:

+ +
if (posX % 13 === 0) {
+  if (sprite === 5) {
+    sprite = 0;
+  } else {
+    sprite++;
+  }
+}
+ +

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 posX (character's position on the screen) by two each frame:

+ +
if(posX > width/2) {
+  newStartPos = -((width/2) + 102);
+  posX = Math.ceil(newStartPos / 13) * 13;
+  console.log(posX);
+} else {
+  posX += 2;
+}
+ +

This is the code that works out how to update the position in each animation frame.

+ +

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 rotateCount by one on each frame instead of two.

+ +

Active learning: a reaction game

+ +

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 A key, and the other with the L key.

+ +

When the Start 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.

+ +

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

+ +

Let's work through this.

+ +
    +
  1. +

    First of all, download the starter file for the app — 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.

    +
  2. +
  3. +

    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:

    + +
    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');
    + +

    In order, these are:

    + +
      +
    1. A reference to our spinner, so we can animate it.
    2. +
    3. A reference to the {{htmlelement("div")}} element that contains the spinner, used for showing and hiding it.
    4. +
    5. A rotate count — how much we want to show the spinner rotated on each frame of the animation.
    6. +
    7. A null start time — will be populated with a start time when the spinner starts spinning.
    8. +
    9. An uninitialized variable to later store the {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} call that animates the spinner.
    10. +
    11. A reference to the Start button.
    12. +
    13. A reference to the results paragraph.
    14. +
    +
  4. +
  5. +

    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.

    + +
    function random(min,max) {
    +  var num = Math.floor(Math.random()*(max-min)) + min;
    +  return num;
    +}
    +
  6. +
  7. +

    Next add in the draw() function, which animates the spinner. This is very similar to the version seen in the simple spinner example we looked at earlier:

    + +
    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);
    +}
    +
  8. +
  9. +

    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 display: none;.

    + +
    result.style.display = 'none';
    +spinnerContainer.style.display = 'none';
    +
  10. +
  11. +

    We'll also define a reset() 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:

    + +
    function reset() {
    +  btn.style.display = 'block';
    +  result.textContent = '';
    +  result.style.display = 'none';
    +}
    +
  12. +
  13. +

    OK, enough preparation.  Let's make the game playable! Add the following block to your code. The start() function calls draw() to start the spinner spinning and display it in the UI, hides the Start button so we can't mess up the game by starting it multiple times concurrently, and runs a setTimeout() call that runs a setEndgame() function after a random interval between 5 and 10 seconds has passed. We also add an event listener to our button to run the start() function when it is clicked.

    + +
    btn.addEventListener('click', start);
    +
    +function start() {
    +  draw();
    +  spinnerContainer.style.display = 'block';
    +  btn.style.display = 'none';
    +  setTimeout(setEndgame, random(5000,10000));
    +}
    + +
    +

    Note: You'll see that in this example we are calling setTimeout() without storing the return value (so not let myTimeout = setTimeout(functionName, interval)). 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.

    +
    + +

    The net result of the previous code is that when the Start 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 setEndgame() function, which we should define next.

    +
  14. +
  15. +

    So add the following function to your code next:

    + +
    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);
    +  };
    +}
    + +

    Stepping through this:

    + +
      +
    1. 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.
    2. +
    3. 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.
    4. +
    5. We then attach a keydown event listener to our document — when any button is pressed down, the keyHandler() function is run.
    6. +
    7. Inside keyHandler(), we include the event object as a parameter (represented by e) — 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.
    8. +
    9. We first log e.key to the console, which is a useful way of finding out the key value of different keys you are pressing.
    10. +
    11. When e.key is "a", we display a message to say that Player 1 won, and when e.key 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 Shift), it is counted as a different key.
    12. +
    13. Regardless of which one of the player control keys was pressed, we remove the keydown 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 setTimeout() to call reset() 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.
    14. +
    +
  16. +
+ +

That's it, you're all done.

+ +
+

Note: If you get stuck, check out our version of the reaction game (see the source code also).

+
+ +

Conclusion

+ +

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.

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

+ +

In this module

+ + 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 +--- +
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
+ +

이 문서에서는 비동기적 프로그래밍과 관련된 몇개의 개념들을 살펴볼 것입니다. 그리고 이것들이 웹브라우저와 자바스크립트에서 어떻게 보이는지도 살펴볼 것입니다. 이 모듈의 다른 문서들을 공부하기 전에, 이 문서에 나와있는 개념들을 먼저 학습하십시오.

+ + + + + + + + + + + + +
선행 조건:기초적인 컴퓨터 언어 능력,  Javascript에 대한 기초적인 이해가 필요합니다. 
학습 목적: 비동기적 프로그래밍이 작동하는 기초적인 원리에 대해 이해하는 것입니다. 그리고 이 개념이 어떻게 웹브라우저와 자바스크립트에서 압도적인 지위를 차지하기 되었는지 알아봅니다. 
+ +

'비동기적'(Asynchronous) 이란?

+ +

일반적으로, 프로그램의 코드는 순차적으로 진행됩니다. 한번에 한가지 사건만 발생하면서 말입니다. 만약 어떤 함수의 결과가 다른 함수에 영향을 받는다면, 그 함수는 다른 함수가 끝나고 값을 산출할 때까지 기다려야 합니다. 그리고 그 과정이 끝날 때 까지, 유저의 입장에서 보자면,  전체 프로그램이 모두 멈춘 것처럼 보입니다. 

+ +

예를들면, 맥 유저라면 종종 회전하는 무지개색 커서(비치볼)를 본 적이 있을 것입니다. 이 커서는 오퍼레이팅 시스템이 이렇게 말하고 있는 것입니다. "당신이 지금 사용하고 있는 시스템은 지금 멈춰서서 뭔가가 끝나기를 기다려야만 합니다. 그리고 이 과정은 당신이 지금 무슨 일이 일어나고있는지 궁금해 할 만큼 오래 걸리고 있습니다."

+ +

Multi-colored macOS beachball busy spinner

+ +

이것은 당황스러운 경험이며, 특히 요즘과 같이 컴퓨터가 여러개 프로세서를 돌리는 시대에는 컴퓨터 성능을 효율적으로 쓰지 못하는 처사입니다. 당신이 다른 코어 프로세서에 다른 작업들을 움직이게 하고 작업이 완료되면 알려줄 수 있을 때, 무언가를 기다리는 것은 의미가 없습니다 .그 동안 다른 작업을 수행할 수 있고, 이것이 비동기 프로그래밍의 기본입니다. 이러한 작업을 비동기적으로 실행할 수 있는 API를 제공하는 것은 당신이 사용하고 있는 프로그래밍 환경(웹 개발의 경우 웹브라우저) 에 달려 있습니다.

+ +

Blocking code

+ +

비동기 기법은 특히 웹 프로그래밍에 매우 유용합니다. 웹 앱이 브라우저에서 특정 코드를 실행하느라 브라우저에게 제어권을 돌려주지 않으면 브라우저는 마치 정지된 것처럼 보일 수 있습니다. 이러한 현상을 blocking 이라고 부릅니다. 자세히 정의하자면, 사용자의 입력을 처리하느라 웹 앱이 프로세서에 대한 제어권을 브라우저에게 반환하지 않는 현상 입니다..

+ +

Blocking의 몇 가지 예를 살펴보겠습니다.

+ +

여기 simple-sync.html 예시가 있습니다. (see it running live), 하나의 버튼에 클릭 이벤트리스너를 지정하여 시간이 오래 걸리는 처리를 하도록하였습니다. (날짜를 천만번 계산하고 마지막에 콘솔에 날짜를 출력합니다.) 그리고 처리가 끝나면 페이지에 간단한 문장을 한 줄 출력합니다. :

+ +
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);
+});
+ +

이 예제를 실행할 때 JavaScript 콘솔을 열고 버튼을 클릭하면, 콘솔에 메시지가 출력되기 전 까지 페이지에 문장이 나타나지 않는다는 것을 알 수 있습니다. 코드는 위에서 아래로 순차적으로 실행되며, 아래쪽 코드는 위쪽 코드의 처리가 끝날 때 까지 실행되지 않습니다.

+ +
+

Note: 앞의 예제는 매우 비현실적입니다. 실제 웹 앱에서 날짜를 천만번 계산할 일은 없습니다. 실제로 보여주기 위해 극단적인 예시를 들었을 뿐입니다..

+
+ +

두 번째 예제를 살펴보겠습니다. simple-sync-ui-blocking.html (see it live),  페이지에 UI가 모두 표시되기 전 까지 사용자의 입력을 막는 좀 더 현실적인 예시입니다. 이번 예시에는 두 가지 버튼을 사용합니다. :

+ + + +
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!')
+);
+ +

첫 번째 버튼을 클릭한 후 두 번째 버튼을 바로 클릭하면 경고 박스가 나타나지 않는 것을 확인할 수 있습니다. 첫 번째 버튼은 이벤트가 끝나기 전 까지 다음 작동을 막아버립니다.

+ +
+

Note: OK, 이 예시가 Blocking 효과를 설명하기에는 구리지만, 실제로 개발자가 겪는 일반적인 문제입니다.

+
+ +

왜 이런 현상이 발생할까요? 답은 자바스크립트는 기본적으로 single threaded이기 때문입니다. 이 시점에서 threads의 개념을 소개할 필요가 있겠군요

+ +

Threads

+ +

Thread 는 기본적으로 프로그램이 작업을 완료하는데 사용할 수 있는 단일 프로세스 입니다. 각 스레드는 한 번에 하나의 작업만 수행할 수 있습니다. :

+ +
Task A --> Task B --> Task C
+ +

위의 예시처럼 각 작업은 순차적으로 실행되며, 다음 작업을 시작하려면 앞의 작업이 완료되어야 합니다.

+ +

앞서 말했듯이, 많은 컴퓨터들이 현재 여러 개의 CPU코어를 가지고 있기 때문에, 한 번에 여러가지 일을 수행할 수 있습니다. Multiple thread를 지원하는 프로그래밍 언어는 멀티코어 컴퓨터의 CPU를 사용하여 여러 작업을 동시에 처리할 수 있습니다. :

+ +
Thread 1: Task A --> Task B
+Thread 2: Task C --> Task D
+ +

JavaScript is single threaded

+ +

자바스크립트는 전통적으로 싱글 thread입니다. 컴퓨터가 여러 개의 CPU를 가지고 있어도 main thread라 불리는 단일 thread에서만 작업을 실행할 수 있습니다. 위의 예시는 아래처럼 실행됩니다. :

+ +
Main thread: Render circles to canvas --> Display alert()
+ +

JavaScript는 이러한 문제를 해결하기 위해 몇 가지 툴을 도입했습니다. Web workers는 여러 개의 JavaScript 청크를 동시에 실행할 수 있도록 worker라고 불리는 별도의 스레드로 보낼 수 있습니다. 따라서 시간이 오래 걸리는 처리는 woker를 사용해 처리하면 blocking 발생을 막을 수 있습니다..

+ +
  Main thread: Task A --> Task C
+Worker thread: Expensive task B
+ +

위의 내용을 잘 기억하시고 다음 예제를 살펴보세요. simple-sync-worker.html (see it running live), JavaScript 콘솔을 함께 열어주세요. 이전 예시는 날짜를 천만 번 계산하고 페이지에 문장을 출력했지만, 이번엔 천만번 계산 전 문장을 페이지에 출력해줍니다. 더이상 첫 번째 작업이 두 번째 작업을 차단하지 않습니다.

+ +

Asynchronous code

+ +

Web worker는 꽤 유용하지만 이들도 한계가 있습니다. 주요한 내용은 Web worker는 {{Glossary("DOM")}} 에 접근할 수 없습니다. — UI를 업데이트하도록 worker에게 어떠한 지시도 직접 할 수 없습니다. 두 번째 예시에서 worker는 100만개의 파란색 원을 만들 수 없습니다. 단순히 숫자만 계산합니다.

+ +

두 번째 문제는 worker에서 실행되는 코드는 차단되지 않지만 동기적으로 실행된다는 것 입니다. 이러한 문제는 함수를 사용할 때 발생합니다. 어떤 함수가 일의 처리를 위해 이전의 여러 프로세스의 결과를 return 받아야 할 경우 입니다. 동기적으로 실행되면 함수 실행에 필요한 매개변수를 받아올 수 없는 경우가 생기므로 함수는 사용자가 원하는 기능을 제대로 실행할 수 없습니다.

+ +
Main thread: Task A --> Task B
+ +

이 예시에서 Task A는 서버로부터 이미지를 가져오고 Task B는 그 이미지에 필터를 적용하는 것과 같은 작업을 수행한다고 가정합니다. Task A를 실행하고 결과를 반환할 시간도 없이 Task B를 실행해버리면 에러가 발생할 것 입니다. 왜냐햐면 Task A에서 이미지를 완전히 가져온 상태가 아니기 때문이죠.

+ +
  Main thread: Task A --> Task B --> |Task D|
+Worker thread: Task C -----------> |      |
+ +

이번 예시에선 Task D가 Task B와 Task C의 결과를 모두 사용한다고 가정합니다. Task B와 Task C가 동시에 아주 빠르게 결과를 반환하면 매우 이상적이겠지만, 현실은 그렇지 않습니다. Task D가 사용될 때 Task B, Task C 둘 중 어느 값이라도 입력이 되지 않을경우 에러가 발생합니다.

+ +

이러한 문제를 해결하기 위해 브라우저를 통해 특정 작업을 비동기적으로 실행할 수 있습니다. 바로 Promises 를 사용하는것 입니다. 아래 예시처럼 Task A가 서버에서 이미지를 가져오는 동안 Task B를 기다리게 할 수 있습니다. :

+ +
Main thread: Task A                   Task B
+    Promise:      |__async operation__|
+ +

위의 작업은 다른 곳에서 처리가 되므로, 비동기 작업이 진행되는 동안 main thread가 차단되지 않습니다.

+ +

이번 문서에서 다룬 내용은 매우 중요한 내용입니다. 다음 문서에선 비동기 코드를 어떻게 쓸 수 있는지 살펴볼 계획이므로 끝까지 읽어주시면 좋겠습니다.

+ +

결론

+ +

현대의 소프트웨어 설계는 프로그램이 한 번에 두 가지 이상의 일을 할 수 있도록 비동기 프로그래밍을 중심으로 돌아가고 있습니다. 보다 새롭고 강력한 API를 이용하면서, 비동기로 작업해야만 하는 사례가 많아질 것입니다. 예전에는 비동기 코드를 쓰기가 힘들었습니다. 여전히 아직 어렵지만, 훨씬 쉬워졌습니다. 이 모듈의 나머지 부분에서는 비동기 코드가 왜 중요한지, 위에서 설명한 일부 문제들을 방지하는 코드 설계 방법에 대해 자세히 알아봅시다.

+ +

이번 module 에서는..

+ + 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 +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
+ +

Javascript에 대한 최신 추가 사항은 ECMAScript 2017 JavaScript 에디션의 일부인 async functions 그리고 await 키워드는 ECMAScript2017에 추가되었습니다. 이 기능들은 기본적으로 비동기 코드를 쓰고 Promise를 더 읽기 더 쉽도록 만들어줍니다. 비동식 코드는 구식 동기코드를 읽기 쉽게 만들기 때문에 학습할 가치가 있습니다. 이 글은 당신이 비동기에 알아야할 것을 알려줍니다.

+ + + + + + + + + + + + +
필수조건:기본 컴퓨터 사용능력, 자바스크립트 기초, 비동기코드에 대한 이해
목표:async/await에 대한 이해
+ +

async/await의 기초

+ +

async/await 코드는 두 가지 부분으로 나눠져있습니다.

+ +

비동기 키워드

+ +

먼저 비동기 함수를 async 함수로 만들기 위하여 function()앞에 async 키워드를 추가합니다. async function()은 await 키워드가 비동기 코드를 호출할 수 있게 해주는 함수 입니다.

+ +

브라우저의 JavaScript 콘솔에서 아래와 같이 입력해보세요. :

+ +
function hello() { return "Hello" };
+hello();
+ +

위의 함수는 "Hello"를 반환합니다. — 특별할게 없죠?

+ +

그러면 함수 앞에 async 키워드를 추가하면 어떻게 될까요?? 아래처럼 작성해봅시다.:

+ +
async function hello() { return "Hello" };
+hello();
+ +

이제 코드가 Promise를 반환합니다. 이것이 async 기능의 특징 중 하나 입니다. — 이 키워드를 사용하면 반환받는 값은 Promise가 됩니다..

+ +

async function expression을 사용하여 아래와 같이 만들 수도 있습니다. :

+ +
let hello = async function() { return "Hello" };
+hello();
+ +

화살표 함수를 사용하면 아래처럼 쓸 수 있습니다. :

+ +
let hello = async () => { return "Hello" };
+ +

기본적으로 두 가지는 모두 같습니다.

+ +

실제로는 fulfil Promise가 반환되기 때문에 반환된 값을 사용하기 위해선 .then() 블럭을 사용해야 합니다. :

+ +
hello().then((value) => console.log(value))
+ +

짧게 표현하면 아래와 같이 쓸 수 있습니다.

+ +
hello().then(console.log)
+ +

이전에 봤던 내용과 비슷하죠?.

+ +

정리하면, async 를 함수와 같이 사용하면 결과를 직접 반환하는게 아니라 Promise를 반환하게 합니다. 또한 동기식 함수는 await사용을 위한 지원과 함께 실행되는 잠재적인 오버헤드를 피할 수 있습니다. 함수가  async라고 선언될 때 필요한 핸들링만 추가하면 JavaScript엔진이 우리가 만든 프로그램을 최적화 할 수 있습니다. 끝내주죠?

+ +

비동기 키워드

+ +

비동기 함수를 await 키워드와 함께 쓰면 그 장점이 확실히 보입니다. 이것은 어떠한 Promise기반 함수 앞에 놓을 수 있습니다. 그리고 우리의 코드의 Promise가 fulfil될 때 까지 잠시 중단하고, 결과를 반환합니다. 그리고 실행을 기다리는 다른 코드들을 중지시키지 않고 그대로 실행되게 합니다.

+ +

await 키워드는 웹 API를 포함하여 Promise를 반환하는 함수를 호출할 때 사용할 수 있습니다.

+ +

여기 간단한 예가 있습니다. :

+ +
async function hello() {
+  return greeting = await Promise.resolve("Hello");
+};
+
+hello().then(alert);
+ +

물론 위의 예시는 그다지 쓸모있진 않습니다. 다만 어떻게 구문을 작성해야 하는지는 잘 나타내줍니다. 이제 실제 사례를 살펴봅시다.

+ +

async/await와 함께 다시 쓰는 promise code

+ +

이전 문서에서 봤던 간단한 fetch() 예제를 살펴봅시다. :

+ +
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);
+});
+ +

지금 시점에서 우리는 Promise가 어떻게 작동하는지 잘 이해하고 있습니다. 그렇다면 지금부터 이 예제를 async/await 를 사용하여 더 간단하게 만들어봅시다. :

+ +
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);
+});
+ +

바꾸고 나니 더 이해하기 쉬워졌습니다. — 더 이상의 .then() 블럭은 찾아 볼 수 없습니다.

+ +

async 키워드가 함수를 Promise로 바꾸었기, 이제 promise 와 await의 하이브리드 접근방식을 사용하기 위해 코드를 리팩토링 할 수 있으며, 두 번째 .then()블럭을 함수 내부의 블럭으로 가져와 더 유연하게 만들 수 있습니다.

+ +
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));
+ +

예제를 직접 만들어보거나, 여기서 결과를 확인할 수 있습니다. 예제 (여기서 소스코드를 볼 수 있습니다.).

+ +

어떻게 작동할까요?

+ +

함수 안에 코드를 작성했고, function 키워드 앞에 async 키워드를 썼다는 것을 알 수 있습니다. 꼭 이렇게 써야합니다!! 비동기 코드를 실행할 블럭을 정의하려면 비동기 함수를 생성해야 합니다. awaitasync function 안에서만 쓸 수 있습니다.

+ +

myFetch() 함수 내에 코드가 이전의 Promise버전과 매우 유사하지만, 다른점이 있습니다. .then()블럭을 사용하여 작업을 이어가는 대신 메서드 호출 전에 await 키워드를 사용하여 반환된 결과를 변수에 할당합니다. await 키워드는 JavaScript 런타임이 이 라인에서 비동기 코드를 일시 중지하여 비동기 함수 호출이 결과를 반환할 때 까지 기다리게 합니다. 그러나 외부의 다른 동기 코드는 실행될 수 있도록 합니다. 작업이 완료되면 코드는 계속 이어져서 실행됩니다. 예를들면 아래와 같습니다. :

+ +
let response = await fetch('coffee.jpg');
+ +

fulfilled된 fetch() Promise에서 반환된 응답은 해당 응답이 사용할 수 있게 되면 response 변수에 할당됩니다. 그리고 parser는 해당 응답이 발생할 때 까지 이 라인에서 일시 중지됩니다. response가 사용 가능하게 되면, parser 는 다음 라인으로 이동하게 되고 그 라인에서 Blob 을 생성하게 됩니다. 이라인도 Promise기반 비동기 메서드를 호출하므로, 여기서도await 을 사용합니다. 비동기 작업 결과가 반환되면, myFetch() 함수가 그 결과를 반환합니다.

+ +

myFetch() 함수를 호출하면, Promise를 반환하므로, 따라서 화면에 Blob을 표시해주는 .then() 코드 블럭 체이닝 할 수 있습니다.

+ +

여기까지 왔으면 이 방법이 멋있다고 생각해야합니다! 왜냐하면 .then() 블럭이 줄어들고 대부분이 동기 코드처럼 보이기 때문에 정말 직관적입니다.

+ +

오류 처리 추가

+ +

그리고 오류 처리를 하려면 몇 가지 옵션이 있습니다.

+ +

동기식 코드에서 쓰는 try...catch 구문을 async/await구조에서 사용할 수 있습니다. 이 예제는 위에서 설명한 첫 번째 코드를 수정한 것 입니다. :

+ +
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();
+ +

catch() {} 블록은 e 라고 부르는 에러 오브젝트를 통과시킵니다. 이제 콘솔에 코드가 던져준 에러 메시지를 출력할 수 있습니다.

+ +

아래 코드는 처음 예제를 리팩토링한 두 번째 버전의 코드 입니다. 이 하이브리드 접근법을 사용하는 코드에서 에러를 탐지하고 싶으면 .catch() 블럭을 .then() 호출의 마지막에 작성합니다. :

+ +
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)
+);
+ +

이는 .catch() 블럭이 async 함수 호출과 Promise 체인 모두에서 발생하는 오류를 잡을 수 있기 때문입니다. 여기서 try/catch 블럭을 사용했더라도, myFetch() 에서 발생한 unhandled에러를 잡아낼 수 있습니다.

+ +

위의 예제 모두를 GitHub에서 찾아볼 수 있습니다. :

+ + + +

Awaiting a Promise.all()

+ +

async/await는 promises의 상위에 만들어져 있기 때문에 Promise의 모든 기능을 사용할 수 있습니다. Promise.all() 을 포함해서 말이죠 — 아래 보이는 코드처럼 Promise.all() 앞에 async키워드를 사용하여 동기식 코드처럼 작성할 수 있습니다. 이전 문서를 확인해봅시다. 이전에 글에서 본 예제. 새로운 버전과 비교하기 위해 탭을 분리 해보세요.

+ +

async/await 스타일로 변경한 코드는 아래와 같습니다. (데모 그리고 소스 코드) :

+ +
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)
+);
+ +

몇 가지 사항을 조금 수정했을 뿐인데 fetchAndDecode()함수를 쉽게 비동기 함수로 변환했습니다. Promise.all() 라인을 살펴보세요:

+ +
let values = await Promise.all([coffee, tea, description]);
+ +

여기에 await 을 사용하여 세 가지 Promise의 결과가 반환되었을 때 values 배열에 담을 수 있습니다. 그리고 마치 동기화 코드처럼 보이죠. 우리가 작업한건 displayContent()async키워드를 추가하고, 모든 코드를.then() 블럭 바깥으로 빼냈습니다. 또한 아주 적은양의 코드 수정도 했죠. 이렇게 하니 더 단순하고, 유용하고 읽기 쉬운 프로그램이 되었습니다.

+ +

마지막으로 에러를 다루기 위해 .catch() 블럭을 displayContent() 함수를 호출하는 곳에 추가했습니다. 이렇게 하면 두 함수에서 발생하는 에러를 처리할 수 있습니다.

+ +
+

Note: finally비동기 블록 대신 비동기 함수 내에서 .finally() 동기 블록을 사용하여 작업이 어떻게 진행되었는지에 대한 최종 보고서를 표시할 수 있습니다. 예제에서도 확인이 가능합니다. 소스 코드).

+
+ +

async/await의 단점

+ +

앞서 봤듯이 async/await은 매우 유용하지만 고려해야 할 몇 가지 단점이 있습니다.

+ +

Async/await 는 우리의 코드를 마치 동기식 코드처럼 보이게 합니다. 그리고 어떤 면에서는 정말로 동기적으로 행동합니다. 함수 블럭에 여러 개의 await 키워드를 사용하면 Promise가 fulfilled되기 전 까지 다음 await 을 차단합니다. 그 동안 다른 태스크는 계속 실행이 되지만 정의한 함수 내에서는 동기적으로 작동할 것 입니다.

+ +

이 말은 우리가 작성한 코드가 바로 이어지는 수 많은 Promise에 의해 느려질 수 있다는 것을 의미합니다. 각 await 는 이전의 작업이 끝날 때 까지 기다립니다(Promise 체이닝과 혼동하지 마세요). 그런데 우리가 원하는건 기다리는게 아니고 일제히 실행되는 것 입니다.

+ +

이 문제를 완화할 수 있는 패턴이 있습니다. — 모든 Promise 오브젝트를 변수에 저장하여 미리 실행되게 하고 변수가 사용 가능할 때 꺼내서 쓰는 것 입니다. 어떻게 작동하는지 한번 살펴봅시다.

+ +

두 가지 예시를 보여 드리겠습니다. — 느린 비동기 작업 slow-async-await.html (소스 코드) 그리고 빠른 비동기 작업 fast-async-await.html (소스 코드)입니다. 두 예제에서 마치 비동기 작업인 것 처럼 보이기 위해 setTimeout() 을 사용했습니다. :

+ +
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
+ }
+ 
+ +

그리고 세 가지 timeoutPromise() 함수를 호출하는 timeTest()함수를 만들었습니다.

+ +
async function timeTest() {
+  ...
+}
+ +

그리고 두 개 예제 모두 시작 시간을 기록하고, timeTest() Promise가 fulfilled된 시간을 저장하여 두 시간의 차를 계산해 작업이 얼마나 걸렸는지 사용자에게 보여줍니다. :

+ +
let startTime = Date.now();
+timeTest().then(() => {
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  alert("Time taken in milliseconds: " + timeTaken);
+})
+ +

timeTest() 함수만 두 예제에서 차이가 있습니다.

+ +

slow-async-await.html 예제이서, timeTest() 함수는 아래와 같이 생겼습니다. :

+ +
async function timeTest() {
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+}
+ +

아주 간단하게 timeoutPromise() 함수를 직접 호출했습니다. 각 작업은 3초씩 걸립니다. 그리고 await 키워드를 사용했기 때문에 이전 await 작업이 끝나야 다음으로 진행됩니다. — 첫 번째 예제를 실행하면, alert 박스에서 약 9초(9000밀리초)가 걸렸음을 확인할 수 있습니다.

+ +

다음으로 fast-async-await.html 예제이서, timeTest() 은 아래와 같이 생겼습니다. :

+ +
async function timeTest() {
+  const timeoutPromise1 = timeoutPromise(3000);
+  const timeoutPromise2 = timeoutPromise(3000);
+  const timeoutPromise3 = timeoutPromise(3000);
+
+  await timeoutPromise1;
+  await timeoutPromise2;
+  await timeoutPromise3;
+}
+ +

여기선 세 가지 Promise 오브젝트를 변수에 저장하여 동시에 작업을 시작하도록 했습니다.

+ +

그리고 그 변수에 await을 사용하여 결과를 호출합니다. — 작업이 거의 동시에 시작됐기 때문에, Promise도 거의 동시에 fulfilled될 것 입니다. 두 번째 예제를 실행하면 거의 3초(3000밀리초) 만에 작업이 끝났음을 확인할 수 있습니다.

+ +

코드를 주의깊게 테스트 하고, 성능이 떨어지기 시작하면 위의 상황을 의심해봐야 합니다.

+ +

다른 아주 사소한 단점은 비동기로 실행될 Promise가 있다면 async함수 안에 항상 await을 써야한다는 것 입니다.

+ +

Async/await class 메서드

+ +

마지막으로 보여줄 내용은 async 키워드를 class/object의 메서드에 사용하여 Promise를 반환하게 만들 수 있다는 것 입니다. 그리고 await 를 그 안에 넣을 수도 있습니다. 다음 문서를 살펴보세요 > ES class code we saw in our object-oriented JavaScript article, 그리고 보이는 코드를 async 메서드로 수정한 아래의 내용과 비교 해보세요 :

+ +
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']);
+ +

이제 클래스의 첫 번째 메서드를 아래와 같이 사용할 수 있습니다. :

+ +
han.greeting().then(console.log);
+ +

브라우저 지원

+ +

async/await 사용 여부를 결정할 때 고려해야 할 한가지 사항은 이전 브라우저에 대한 지원입니다. + promises와 마찬가지로 대부분의 최신 브라우저에서 사용할 수 있습니다. + 주요 지원 문제는 Internet Explorer 그리고 Opera Mini에서 발생합니다.

+ +

async/await을 사용하는데 브라우저 지원이 걱정되는 경우 BabelJS 라이브러리를 사용하는 것을 고려해 볼 수 있습니다. BabelJS는 최신 자바스크립트를 사용하여 애플리케이션을 작성하고 사용자 브라우저에 필요한 변경사항을 Babel이 파악할 수 있도록 지원합니다. async/await를 지원하지 않는 브라우저를 만나면 Babel은 이전 브라우저에서 작동하는 polyfill를 자동으로 제공합니다.

+ +

결론

+ +

async/await를 사용하면 읽기 쉽고 유지보수가 편리한 비동기 코드를 간단하게 작성할 수 있습니다. 브라우저 지원이 다른 비동기 코드에 비해 제한적이기는 하지만 현재는 물론 미래에도 사용을 위해 배울 가치는 충분합니다.

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

+ +

이 모듈에서

+ + diff --git a/files/ko/learn/javascript/asynchronous/async_await/index.html b/files/ko/learn/javascript/asynchronous/async_await/index.html deleted file mode 100644 index 92a9d81dc6..0000000000 --- a/files/ko/learn/javascript/asynchronous/async_await/index.html +++ /dev/null @@ -1,413 +0,0 @@ ---- -title: async와 await를 사용하여 비동기 프로그래밍을 쉽게 만들기 -slug: Learn/JavaScript/Asynchronous/Async_await -tags: - - Beginner - - CodingScripting - - Guide - - JavaScript - - Learn - - Promises - - async - - asynchronous - - await ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
- -

Javascript에 대한 최신 추가 사항은 ECMAScript 2017 JavaScript 에디션의 일부인 async functions 그리고 await 키워드는 ECMAScript2017에 추가되었습니다. 이 기능들은 기본적으로 비동기 코드를 쓰고 Promise를 더 읽기 더 쉽도록 만들어줍니다. 비동식 코드는 구식 동기코드를 읽기 쉽게 만들기 때문에 학습할 가치가 있습니다. 이 글은 당신이 비동기에 알아야할 것을 알려줍니다.

- - - - - - - - - - - - -
필수조건:기본 컴퓨터 사용능력, 자바스크립트 기초, 비동기코드에 대한 이해
목표:async/await에 대한 이해
- -

async/await의 기초

- -

async/await 코드는 두 가지 부분으로 나눠져있습니다.

- -

비동기 키워드

- -

먼저 비동기 함수를 async 함수로 만들기 위하여 function()앞에 async 키워드를 추가합니다. async function()은 await 키워드가 비동기 코드를 호출할 수 있게 해주는 함수 입니다.

- -

브라우저의 JavaScript 콘솔에서 아래와 같이 입력해보세요. :

- -
function hello() { return "Hello" };
-hello();
- -

위의 함수는 "Hello"를 반환합니다. — 특별할게 없죠?

- -

그러면 함수 앞에 async 키워드를 추가하면 어떻게 될까요?? 아래처럼 작성해봅시다.:

- -
async function hello() { return "Hello" };
-hello();
- -

이제 코드가 Promise를 반환합니다. 이것이 async 기능의 특징 중 하나 입니다. — 이 키워드를 사용하면 반환받는 값은 Promise가 됩니다..

- -

async function expression을 사용하여 아래와 같이 만들 수도 있습니다. :

- -
let hello = async function() { return "Hello" };
-hello();
- -

화살표 함수를 사용하면 아래처럼 쓸 수 있습니다. :

- -
let hello = async () => { return "Hello" };
- -

기본적으로 두 가지는 모두 같습니다.

- -

실제로는 fulfil Promise가 반환되기 때문에 반환된 값을 사용하기 위해선 .then() 블럭을 사용해야 합니다. :

- -
hello().then((value) => console.log(value))
- -

짧게 표현하면 아래와 같이 쓸 수 있습니다.

- -
hello().then(console.log)
- -

이전에 봤던 내용과 비슷하죠?.

- -

정리하면, async 를 함수와 같이 사용하면 결과를 직접 반환하는게 아니라 Promise를 반환하게 합니다. 또한 동기식 함수는 await사용을 위한 지원과 함께 실행되는 잠재적인 오버헤드를 피할 수 있습니다. 함수가  async라고 선언될 때 필요한 핸들링만 추가하면 JavaScript엔진이 우리가 만든 프로그램을 최적화 할 수 있습니다. 끝내주죠?

- -

비동기 키워드

- -

비동기 함수를 await 키워드와 함께 쓰면 그 장점이 확실히 보입니다. 이것은 어떠한 Promise기반 함수 앞에 놓을 수 있습니다. 그리고 우리의 코드의 Promise가 fulfil될 때 까지 잠시 중단하고, 결과를 반환합니다. 그리고 실행을 기다리는 다른 코드들을 중지시키지 않고 그대로 실행되게 합니다.

- -

await 키워드는 웹 API를 포함하여 Promise를 반환하는 함수를 호출할 때 사용할 수 있습니다.

- -

여기 간단한 예가 있습니다. :

- -
async function hello() {
-  return greeting = await Promise.resolve("Hello");
-};
-
-hello().then(alert);
- -

물론 위의 예시는 그다지 쓸모있진 않습니다. 다만 어떻게 구문을 작성해야 하는지는 잘 나타내줍니다. 이제 실제 사례를 살펴봅시다.

- -

async/await와 함께 다시 쓰는 promise code

- -

이전 문서에서 봤던 간단한 fetch() 예제를 살펴봅시다. :

- -
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);
-});
- -

지금 시점에서 우리는 Promise가 어떻게 작동하는지 잘 이해하고 있습니다. 그렇다면 지금부터 이 예제를 async/await 를 사용하여 더 간단하게 만들어봅시다. :

- -
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);
-});
- -

바꾸고 나니 더 이해하기 쉬워졌습니다. — 더 이상의 .then() 블럭은 찾아 볼 수 없습니다.

- -

async 키워드가 함수를 Promise로 바꾸었기, 이제 promise 와 await의 하이브리드 접근방식을 사용하기 위해 코드를 리팩토링 할 수 있으며, 두 번째 .then()블럭을 함수 내부의 블럭으로 가져와 더 유연하게 만들 수 있습니다.

- -
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));
- -

예제를 직접 만들어보거나, 여기서 결과를 확인할 수 있습니다. 예제 (여기서 소스코드를 볼 수 있습니다.).

- -

어떻게 작동할까요?

- -

함수 안에 코드를 작성했고, function 키워드 앞에 async 키워드를 썼다는 것을 알 수 있습니다. 꼭 이렇게 써야합니다!! 비동기 코드를 실행할 블럭을 정의하려면 비동기 함수를 생성해야 합니다. awaitasync function 안에서만 쓸 수 있습니다.

- -

myFetch() 함수 내에 코드가 이전의 Promise버전과 매우 유사하지만, 다른점이 있습니다. .then()블럭을 사용하여 작업을 이어가는 대신 메서드 호출 전에 await 키워드를 사용하여 반환된 결과를 변수에 할당합니다. await 키워드는 JavaScript 런타임이 이 라인에서 비동기 코드를 일시 중지하여 비동기 함수 호출이 결과를 반환할 때 까지 기다리게 합니다. 그러나 외부의 다른 동기 코드는 실행될 수 있도록 합니다. 작업이 완료되면 코드는 계속 이어져서 실행됩니다. 예를들면 아래와 같습니다. :

- -
let response = await fetch('coffee.jpg');
- -

fulfilled된 fetch() Promise에서 반환된 응답은 해당 응답이 사용할 수 있게 되면 response 변수에 할당됩니다. 그리고 parser는 해당 응답이 발생할 때 까지 이 라인에서 일시 중지됩니다. response가 사용 가능하게 되면, parser 는 다음 라인으로 이동하게 되고 그 라인에서 Blob 을 생성하게 됩니다. 이라인도 Promise기반 비동기 메서드를 호출하므로, 여기서도await 을 사용합니다. 비동기 작업 결과가 반환되면, myFetch() 함수가 그 결과를 반환합니다.

- -

myFetch() 함수를 호출하면, Promise를 반환하므로, 따라서 화면에 Blob을 표시해주는 .then() 코드 블럭 체이닝 할 수 있습니다.

- -

여기까지 왔으면 이 방법이 멋있다고 생각해야합니다! 왜냐하면 .then() 블럭이 줄어들고 대부분이 동기 코드처럼 보이기 때문에 정말 직관적입니다.

- -

오류 처리 추가

- -

그리고 오류 처리를 하려면 몇 가지 옵션이 있습니다.

- -

동기식 코드에서 쓰는 try...catch 구문을 async/await구조에서 사용할 수 있습니다. 이 예제는 위에서 설명한 첫 번째 코드를 수정한 것 입니다. :

- -
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();
- -

catch() {} 블록은 e 라고 부르는 에러 오브젝트를 통과시킵니다. 이제 콘솔에 코드가 던져준 에러 메시지를 출력할 수 있습니다.

- -

아래 코드는 처음 예제를 리팩토링한 두 번째 버전의 코드 입니다. 이 하이브리드 접근법을 사용하는 코드에서 에러를 탐지하고 싶으면 .catch() 블럭을 .then() 호출의 마지막에 작성합니다. :

- -
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)
-);
- -

이는 .catch() 블럭이 async 함수 호출과 Promise 체인 모두에서 발생하는 오류를 잡을 수 있기 때문입니다. 여기서 try/catch 블럭을 사용했더라도, myFetch() 에서 발생한 unhandled에러를 잡아낼 수 있습니다.

- -

위의 예제 모두를 GitHub에서 찾아볼 수 있습니다. :

- - - -

Awaiting a Promise.all()

- -

async/await는 promises의 상위에 만들어져 있기 때문에 Promise의 모든 기능을 사용할 수 있습니다. Promise.all() 을 포함해서 말이죠 — 아래 보이는 코드처럼 Promise.all() 앞에 async키워드를 사용하여 동기식 코드처럼 작성할 수 있습니다. 이전 문서를 확인해봅시다. 이전에 글에서 본 예제. 새로운 버전과 비교하기 위해 탭을 분리 해보세요.

- -

async/await 스타일로 변경한 코드는 아래와 같습니다. (데모 그리고 소스 코드) :

- -
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)
-);
- -

몇 가지 사항을 조금 수정했을 뿐인데 fetchAndDecode()함수를 쉽게 비동기 함수로 변환했습니다. Promise.all() 라인을 살펴보세요:

- -
let values = await Promise.all([coffee, tea, description]);
- -

여기에 await 을 사용하여 세 가지 Promise의 결과가 반환되었을 때 values 배열에 담을 수 있습니다. 그리고 마치 동기화 코드처럼 보이죠. 우리가 작업한건 displayContent()async키워드를 추가하고, 모든 코드를.then() 블럭 바깥으로 빼냈습니다. 또한 아주 적은양의 코드 수정도 했죠. 이렇게 하니 더 단순하고, 유용하고 읽기 쉬운 프로그램이 되었습니다.

- -

마지막으로 에러를 다루기 위해 .catch() 블럭을 displayContent() 함수를 호출하는 곳에 추가했습니다. 이렇게 하면 두 함수에서 발생하는 에러를 처리할 수 있습니다.

- -
-

Note: finally비동기 블록 대신 비동기 함수 내에서 .finally() 동기 블록을 사용하여 작업이 어떻게 진행되었는지에 대한 최종 보고서를 표시할 수 있습니다. 예제에서도 확인이 가능합니다. 소스 코드).

-
- -

async/await의 단점

- -

앞서 봤듯이 async/await은 매우 유용하지만 고려해야 할 몇 가지 단점이 있습니다.

- -

Async/await 는 우리의 코드를 마치 동기식 코드처럼 보이게 합니다. 그리고 어떤 면에서는 정말로 동기적으로 행동합니다. 함수 블럭에 여러 개의 await 키워드를 사용하면 Promise가 fulfilled되기 전 까지 다음 await 을 차단합니다. 그 동안 다른 태스크는 계속 실행이 되지만 정의한 함수 내에서는 동기적으로 작동할 것 입니다.

- -

이 말은 우리가 작성한 코드가 바로 이어지는 수 많은 Promise에 의해 느려질 수 있다는 것을 의미합니다. 각 await 는 이전의 작업이 끝날 때 까지 기다립니다(Promise 체이닝과 혼동하지 마세요). 그런데 우리가 원하는건 기다리는게 아니고 일제히 실행되는 것 입니다.

- -

이 문제를 완화할 수 있는 패턴이 있습니다. — 모든 Promise 오브젝트를 변수에 저장하여 미리 실행되게 하고 변수가 사용 가능할 때 꺼내서 쓰는 것 입니다. 어떻게 작동하는지 한번 살펴봅시다.

- -

두 가지 예시를 보여 드리겠습니다. — 느린 비동기 작업 slow-async-await.html (소스 코드) 그리고 빠른 비동기 작업 fast-async-await.html (소스 코드)입니다. 두 예제에서 마치 비동기 작업인 것 처럼 보이기 위해 setTimeout() 을 사용했습니다. :

- -
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
- }
- 
- -

그리고 세 가지 timeoutPromise() 함수를 호출하는 timeTest()함수를 만들었습니다.

- -
async function timeTest() {
-  ...
-}
- -

그리고 두 개 예제 모두 시작 시간을 기록하고, timeTest() Promise가 fulfilled된 시간을 저장하여 두 시간의 차를 계산해 작업이 얼마나 걸렸는지 사용자에게 보여줍니다. :

- -
let startTime = Date.now();
-timeTest().then(() => {
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  alert("Time taken in milliseconds: " + timeTaken);
-})
- -

timeTest() 함수만 두 예제에서 차이가 있습니다.

- -

slow-async-await.html 예제이서, timeTest() 함수는 아래와 같이 생겼습니다. :

- -
async function timeTest() {
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-}
- -

아주 간단하게 timeoutPromise() 함수를 직접 호출했습니다. 각 작업은 3초씩 걸립니다. 그리고 await 키워드를 사용했기 때문에 이전 await 작업이 끝나야 다음으로 진행됩니다. — 첫 번째 예제를 실행하면, alert 박스에서 약 9초(9000밀리초)가 걸렸음을 확인할 수 있습니다.

- -

다음으로 fast-async-await.html 예제이서, timeTest() 은 아래와 같이 생겼습니다. :

- -
async function timeTest() {
-  const timeoutPromise1 = timeoutPromise(3000);
-  const timeoutPromise2 = timeoutPromise(3000);
-  const timeoutPromise3 = timeoutPromise(3000);
-
-  await timeoutPromise1;
-  await timeoutPromise2;
-  await timeoutPromise3;
-}
- -

여기선 세 가지 Promise 오브젝트를 변수에 저장하여 동시에 작업을 시작하도록 했습니다.

- -

그리고 그 변수에 await을 사용하여 결과를 호출합니다. — 작업이 거의 동시에 시작됐기 때문에, Promise도 거의 동시에 fulfilled될 것 입니다. 두 번째 예제를 실행하면 거의 3초(3000밀리초) 만에 작업이 끝났음을 확인할 수 있습니다.

- -

코드를 주의깊게 테스트 하고, 성능이 떨어지기 시작하면 위의 상황을 의심해봐야 합니다.

- -

다른 아주 사소한 단점은 비동기로 실행될 Promise가 있다면 async함수 안에 항상 await을 써야한다는 것 입니다.

- -

Async/await class 메서드

- -

마지막으로 보여줄 내용은 async 키워드를 class/object의 메서드에 사용하여 Promise를 반환하게 만들 수 있다는 것 입니다. 그리고 await 를 그 안에 넣을 수도 있습니다. 다음 문서를 살펴보세요 > ES class code we saw in our object-oriented JavaScript article, 그리고 보이는 코드를 async 메서드로 수정한 아래의 내용과 비교 해보세요 :

- -
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']);
- -

이제 클래스의 첫 번째 메서드를 아래와 같이 사용할 수 있습니다. :

- -
han.greeting().then(console.log);
- -

브라우저 지원

- -

async/await 사용 여부를 결정할 때 고려해야 할 한가지 사항은 이전 브라우저에 대한 지원입니다. - promises와 마찬가지로 대부분의 최신 브라우저에서 사용할 수 있습니다. - 주요 지원 문제는 Internet Explorer 그리고 Opera Mini에서 발생합니다.

- -

async/await을 사용하는데 브라우저 지원이 걱정되는 경우 BabelJS 라이브러리를 사용하는 것을 고려해 볼 수 있습니다. BabelJS는 최신 자바스크립트를 사용하여 애플리케이션을 작성하고 사용자 브라우저에 필요한 변경사항을 Babel이 파악할 수 있도록 지원합니다. async/await를 지원하지 않는 브라우저를 만나면 Babel은 이전 브라우저에서 작동하는 polyfill를 자동으로 제공합니다.

- -

결론

- -

async/await를 사용하면 읽기 쉽고 유지보수가 편리한 비동기 코드를 간단하게 작성할 수 있습니다. 브라우저 지원이 다른 비동기 코드에 비해 제한적이기는 하지만 현재는 물론 미래에도 사용을 위해 배울 가치는 충분합니다.

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

- -

이 모듈에서

- - diff --git a/files/ko/learn/javascript/asynchronous/concepts/index.html b/files/ko/learn/javascript/asynchronous/concepts/index.html deleted file mode 100644 index 8b1a12bf46..0000000000 --- a/files/ko/learn/javascript/asynchronous/concepts/index.html +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: 일반적인 비동기 프로그래밍 개념 -slug: Learn/JavaScript/Asynchronous/Concepts -tags: - - 비동기 - - 비동기 프로그래밍 - - 자바스크립트 -translation_of: Learn/JavaScript/Asynchronous/Concepts ---- -
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
- -

이 문서에서는 비동기적 프로그래밍과 관련된 몇개의 개념들을 살펴볼 것입니다. 그리고 이것들이 웹브라우저와 자바스크립트에서 어떻게 보이는지도 살펴볼 것입니다. 이 모듈의 다른 문서들을 공부하기 전에, 이 문서에 나와있는 개념들을 먼저 학습하십시오.

- - - - - - - - - - - - -
선행 조건:기초적인 컴퓨터 언어 능력,  Javascript에 대한 기초적인 이해가 필요합니다. 
학습 목적: 비동기적 프로그래밍이 작동하는 기초적인 원리에 대해 이해하는 것입니다. 그리고 이 개념이 어떻게 웹브라우저와 자바스크립트에서 압도적인 지위를 차지하기 되었는지 알아봅니다. 
- -

'비동기적'(Asynchronous) 이란?

- -

일반적으로, 프로그램의 코드는 순차적으로 진행됩니다. 한번에 한가지 사건만 발생하면서 말입니다. 만약 어떤 함수의 결과가 다른 함수에 영향을 받는다면, 그 함수는 다른 함수가 끝나고 값을 산출할 때까지 기다려야 합니다. 그리고 그 과정이 끝날 때 까지, 유저의 입장에서 보자면,  전체 프로그램이 모두 멈춘 것처럼 보입니다. 

- -

예를들면, 맥 유저라면 종종 회전하는 무지개색 커서(비치볼)를 본 적이 있을 것입니다. 이 커서는 오퍼레이팅 시스템이 이렇게 말하고 있는 것입니다. "당신이 지금 사용하고 있는 시스템은 지금 멈춰서서 뭔가가 끝나기를 기다려야만 합니다. 그리고 이 과정은 당신이 지금 무슨 일이 일어나고있는지 궁금해 할 만큼 오래 걸리고 있습니다."

- -

Multi-colored macOS beachball busy spinner

- -

이것은 당황스러운 경험이며, 특히 요즘과 같이 컴퓨터가 여러개 프로세서를 돌리는 시대에는 컴퓨터 성능을 효율적으로 쓰지 못하는 처사입니다. 당신이 다른 코어 프로세서에 다른 작업들을 움직이게 하고 작업이 완료되면 알려줄 수 있을 때, 무언가를 기다리는 것은 의미가 없습니다 .그 동안 다른 작업을 수행할 수 있고, 이것이 비동기 프로그래밍의 기본입니다. 이러한 작업을 비동기적으로 실행할 수 있는 API를 제공하는 것은 당신이 사용하고 있는 프로그래밍 환경(웹 개발의 경우 웹브라우저) 에 달려 있습니다.

- -

Blocking code

- -

비동기 기법은 특히 웹 프로그래밍에 매우 유용합니다. 웹 앱이 브라우저에서 특정 코드를 실행하느라 브라우저에게 제어권을 돌려주지 않으면 브라우저는 마치 정지된 것처럼 보일 수 있습니다. 이러한 현상을 blocking 이라고 부릅니다. 자세히 정의하자면, 사용자의 입력을 처리하느라 웹 앱이 프로세서에 대한 제어권을 브라우저에게 반환하지 않는 현상 입니다..

- -

Blocking의 몇 가지 예를 살펴보겠습니다.

- -

여기 simple-sync.html 예시가 있습니다. (see it running live), 하나의 버튼에 클릭 이벤트리스너를 지정하여 시간이 오래 걸리는 처리를 하도록하였습니다. (날짜를 천만번 계산하고 마지막에 콘솔에 날짜를 출력합니다.) 그리고 처리가 끝나면 페이지에 간단한 문장을 한 줄 출력합니다. :

- -
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);
-});
- -

이 예제를 실행할 때 JavaScript 콘솔을 열고 버튼을 클릭하면, 콘솔에 메시지가 출력되기 전 까지 페이지에 문장이 나타나지 않는다는 것을 알 수 있습니다. 코드는 위에서 아래로 순차적으로 실행되며, 아래쪽 코드는 위쪽 코드의 처리가 끝날 때 까지 실행되지 않습니다.

- -
-

Note: 앞의 예제는 매우 비현실적입니다. 실제 웹 앱에서 날짜를 천만번 계산할 일은 없습니다. 실제로 보여주기 위해 극단적인 예시를 들었을 뿐입니다..

-
- -

두 번째 예제를 살펴보겠습니다. simple-sync-ui-blocking.html (see it live),  페이지에 UI가 모두 표시되기 전 까지 사용자의 입력을 막는 좀 더 현실적인 예시입니다. 이번 예시에는 두 가지 버튼을 사용합니다. :

- - - -
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!')
-);
- -

첫 번째 버튼을 클릭한 후 두 번째 버튼을 바로 클릭하면 경고 박스가 나타나지 않는 것을 확인할 수 있습니다. 첫 번째 버튼은 이벤트가 끝나기 전 까지 다음 작동을 막아버립니다.

- -
-

Note: OK, 이 예시가 Blocking 효과를 설명하기에는 구리지만, 실제로 개발자가 겪는 일반적인 문제입니다.

-
- -

왜 이런 현상이 발생할까요? 답은 자바스크립트는 기본적으로 single threaded이기 때문입니다. 이 시점에서 threads의 개념을 소개할 필요가 있겠군요

- -

Threads

- -

Thread 는 기본적으로 프로그램이 작업을 완료하는데 사용할 수 있는 단일 프로세스 입니다. 각 스레드는 한 번에 하나의 작업만 수행할 수 있습니다. :

- -
Task A --> Task B --> Task C
- -

위의 예시처럼 각 작업은 순차적으로 실행되며, 다음 작업을 시작하려면 앞의 작업이 완료되어야 합니다.

- -

앞서 말했듯이, 많은 컴퓨터들이 현재 여러 개의 CPU코어를 가지고 있기 때문에, 한 번에 여러가지 일을 수행할 수 있습니다. Multiple thread를 지원하는 프로그래밍 언어는 멀티코어 컴퓨터의 CPU를 사용하여 여러 작업을 동시에 처리할 수 있습니다. :

- -
Thread 1: Task A --> Task B
-Thread 2: Task C --> Task D
- -

JavaScript is single threaded

- -

자바스크립트는 전통적으로 싱글 thread입니다. 컴퓨터가 여러 개의 CPU를 가지고 있어도 main thread라 불리는 단일 thread에서만 작업을 실행할 수 있습니다. 위의 예시는 아래처럼 실행됩니다. :

- -
Main thread: Render circles to canvas --> Display alert()
- -

JavaScript는 이러한 문제를 해결하기 위해 몇 가지 툴을 도입했습니다. Web workers는 여러 개의 JavaScript 청크를 동시에 실행할 수 있도록 worker라고 불리는 별도의 스레드로 보낼 수 있습니다. 따라서 시간이 오래 걸리는 처리는 woker를 사용해 처리하면 blocking 발생을 막을 수 있습니다..

- -
  Main thread: Task A --> Task C
-Worker thread: Expensive task B
- -

위의 내용을 잘 기억하시고 다음 예제를 살펴보세요. simple-sync-worker.html (see it running live), JavaScript 콘솔을 함께 열어주세요. 이전 예시는 날짜를 천만 번 계산하고 페이지에 문장을 출력했지만, 이번엔 천만번 계산 전 문장을 페이지에 출력해줍니다. 더이상 첫 번째 작업이 두 번째 작업을 차단하지 않습니다.

- -

Asynchronous code

- -

Web worker는 꽤 유용하지만 이들도 한계가 있습니다. 주요한 내용은 Web worker는 {{Glossary("DOM")}} 에 접근할 수 없습니다. — UI를 업데이트하도록 worker에게 어떠한 지시도 직접 할 수 없습니다. 두 번째 예시에서 worker는 100만개의 파란색 원을 만들 수 없습니다. 단순히 숫자만 계산합니다.

- -

두 번째 문제는 worker에서 실행되는 코드는 차단되지 않지만 동기적으로 실행된다는 것 입니다. 이러한 문제는 함수를 사용할 때 발생합니다. 어떤 함수가 일의 처리를 위해 이전의 여러 프로세스의 결과를 return 받아야 할 경우 입니다. 동기적으로 실행되면 함수 실행에 필요한 매개변수를 받아올 수 없는 경우가 생기므로 함수는 사용자가 원하는 기능을 제대로 실행할 수 없습니다.

- -
Main thread: Task A --> Task B
- -

이 예시에서 Task A는 서버로부터 이미지를 가져오고 Task B는 그 이미지에 필터를 적용하는 것과 같은 작업을 수행한다고 가정합니다. Task A를 실행하고 결과를 반환할 시간도 없이 Task B를 실행해버리면 에러가 발생할 것 입니다. 왜냐햐면 Task A에서 이미지를 완전히 가져온 상태가 아니기 때문이죠.

- -
  Main thread: Task A --> Task B --> |Task D|
-Worker thread: Task C -----------> |      |
- -

이번 예시에선 Task D가 Task B와 Task C의 결과를 모두 사용한다고 가정합니다. Task B와 Task C가 동시에 아주 빠르게 결과를 반환하면 매우 이상적이겠지만, 현실은 그렇지 않습니다. Task D가 사용될 때 Task B, Task C 둘 중 어느 값이라도 입력이 되지 않을경우 에러가 발생합니다.

- -

이러한 문제를 해결하기 위해 브라우저를 통해 특정 작업을 비동기적으로 실행할 수 있습니다. 바로 Promises 를 사용하는것 입니다. 아래 예시처럼 Task A가 서버에서 이미지를 가져오는 동안 Task B를 기다리게 할 수 있습니다. :

- -
Main thread: Task A                   Task B
-    Promise:      |__async operation__|
- -

위의 작업은 다른 곳에서 처리가 되므로, 비동기 작업이 진행되는 동안 main thread가 차단되지 않습니다.

- -

이번 문서에서 다룬 내용은 매우 중요한 내용입니다. 다음 문서에선 비동기 코드를 어떻게 쓸 수 있는지 살펴볼 계획이므로 끝까지 읽어주시면 좋겠습니다.

- -

결론

- -

현대의 소프트웨어 설계는 프로그램이 한 번에 두 가지 이상의 일을 할 수 있도록 비동기 프로그래밍을 중심으로 돌아가고 있습니다. 보다 새롭고 강력한 API를 이용하면서, 비동기로 작업해야만 하는 사례가 많아질 것입니다. 예전에는 비동기 코드를 쓰기가 힘들었습니다. 여전히 아직 어렵지만, 훨씬 쉬워졌습니다. 이 모듈의 나머지 부분에서는 비동기 코드가 왜 중요한지, 위에서 설명한 일부 문제들을 방지하는 코드 설계 방법에 대해 자세히 알아봅시다.

- -

이번 module 에서는..

- - diff --git a/files/ko/learn/javascript/asynchronous/timeouts_and_intervals/index.html b/files/ko/learn/javascript/asynchronous/timeouts_and_intervals/index.html deleted file mode 100644 index 378f0a5f55..0000000000 --- a/files/ko/learn/javascript/asynchronous/timeouts_and_intervals/index.html +++ /dev/null @@ -1,598 +0,0 @@ ---- -title: 'Cooperative asynchronous JavaScript: Timeouts and intervals' -slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals -translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
- -

이 장에서는 자바스크립트가 설정된 시간이 경과하거나 혹은 정해진 시간 간격(예 : 초당 설정된 횟수)으로 비동기 코드를 작동하는 전형적인 방법을 살펴본다. 그리고 이 방법들이 어떤 것에 유용한지 얘기해 보고, 그 본질적인 문제에 대해 살펴본다.

- - - - - - - - - - - - -
Prerequisites:Basic computer literacy, a reasonable understanding of JavaScript fundamentals.
Objective:To understand asynchronous loops and intervals and what they are useful for.
- -

Introduction

- -

오랜 시간 동안 웹플랫폼은 자바스크립트 프로그래머가 일정한 시간이 흐른 뒤에 비동기적 코드를 실행할 수 있게하는 다양한 함수들을 제공해 왔다. 그리고 프로그래머가 중지시킬 때까지 코드 블록을 반복적으로 실행하기 위한 다음과 같은 함수들이 있다.

- -
-
setTimeout()
-
특정 시간이 경과한 뒤에 특정 코드블록을 한번 실행한다.
-
-
setInterval()
-
각각의 호출 간에 일정한 시간 간격으로 특정 코드블록을 반복적으로 실행한다.
-
requestAnimationFrame()
-
setInterval()의 최신 버전; 그 코드가 실행되는 환경에 관계없이 적절한 프레임 속도로 애니메이션을 실행시키면서, 브라우저가 다음 화면을 보여주기 전에 특정 코드블록을 실행한다.
-
- -

이 함수들에 의해 설정된 비동기 코드는 메인 스레드에서 작동한다. 그러나 프로세서가 이러한 작업을 얼마나 많이 수행하는지에 따라, 코드가 반복되는 중간에 다른 코드를 어느 정도 효율적으로 실행할 수 있다. 어쨌든 이러한 함수들은 웹사이트나 응용 프로그램에서 일정한 애니메이션 및 기타 배경 처리를 실행하는 데 사용된다. 다음 섹션에서는 그것들을 어떻게 사용할 수 있는지 보여줄 것이다.

- -

setTimeout()

- -

앞에서 언급했듯이 setTimeout ()은 지정된 시간이 경과 한 후 특정 코드 블록을 한 번 실행한다. 그리고 다음과 같은 파라미터가 필요하다.:

- - - -
-

Note: 타임아웃 콜백은 단독으로 실행되지 않기 때문에 지정된 시간이 지난 그 시점에 정확히  콜백 될 것이라는 보장은 없다. 그보다는 최소한 그 정도의 시간이 지난 후에 호출된다. 메인 스레드가 실행해야 할 핸들러를 찾기 위해 이런 핸들러들을 살펴보는 시점에 도달할 때까지 타임아웃 핸들러를 실행할 수 없다.

-
- -

아래 예제에서 브라우저는 2초가 지나면 익명의 함수를 실행하고 경보 메시지를 띄울 것이다. (see it running live, and see the source code):

- -
let myGreeting = setTimeout(function() {
-  alert('Hello, Mr. Universe!');
-}, 2000)
- -

지정한 함수가 꼭 익명일 필요는 없다. 함수에 이름을 부여 할 수 있고, 다른 곳에서 함수를 정의하고 setTimeout ()에 참조(reference)를 전달할 수도 있다. 아래 코드는 위의 코드와 같은 실행 결과를 얻을 수 있다. 

- -
// 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);
- -

예를 들자면 timeout 함수와 이벤트에 의해 중복 호출되는 함수를 사용하려면 이 방법이 유용할 수 있다. 이 방법은 코드라인을 깔끔하게  정리하는 데 도움을 준다. 특히 timeout 콜백의 코드라인이 여러 줄이라면 더욱 그렇다.  

- -

setTimeout ()은 나중에 타임아웃을 할 경우에 타임아웃을 참조하는데 사용하는 식별자 값을 리턴한다.  그 방법을 알아 보려면 아래{{anch("Clearing timeouts")}}을 참조하세요.

- -

setTimeout () 함수에 매개변수(parameter) 전달

- -

setTimeout () 내에서 실행되는 함수에 전달하려는 모든 매개변수는 setTimeout () 매개변수 목록 끝에 추가하여 전달해야 한다. 아래 예제처럼, 이전 함수를 리팩터링하여 전달된 매개변수의 사람 이름이 추가된 문장을 표시할 수 있다.

- -
function sayHi(who) {
-  alert('Hello ' + who + '!');
-}
- -

Say hello의 대상이 되는 사람이름은 setTimeout()의 세번째 매개변수로 함수에 전달된다.

- -
let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');
- -

timeout 취소

- -

마지막으로 타임아웃이 생성되면(setTimeout()이 실행되면) 특정시간이 경과하기 전에 clearTimeout()을 호출하여 타임아웃을 취소할 수 있다.  clearTimeout()은  setTimeout()콜의 식별자를 매개변수로  setTimeout()에 전달한다. 위 예제의 타임아웃을 취소하려면 아래와 같이 하면 된다.

- -
clearTimeout(myGreeting);
- -
-

Note: 인사를 할 사람의 이름을 설정하고 별도의 버튼을 사용하여 인사말을 취소 할 수있는 약간 더 복잡한 폼양식 예제인  greeter-app.html 을 참조하세요.

-
- -

setInterval()

- -

setTimeout ()은 일정 시간이 지난 후 코드를 한 번 실행해야 할 때 완벽하게 작동합니다. 그러나 애니메이션의 경우와 같이 코드를 반복해서 실행해야 할 경우 어떨까요?

- -

이럴 경우에 setInterval ()이 필요합니다. setInterval ()은 setTimeout ()과 매우 유사한 방식으로 작동합니다. 다만 setTimeout ()처럼 첫 번째 매개 변수(함수)가 타임아웃 후에 한번 실행되는게 아니라 두 번째 매개 변수에 주어진 시간까지 반복적으로 실행되는 것이 차이점입니다.  setInterval() 호출의 후속 파라미터로 실행 중인 함수에 필요한 파라미터를 전달할 수도 있다.

- -

예를 들어 봅시다. 다음 함수는 새 Date() 객체를 생성한 후에 ToLocaleTimeString()을 사용하여 시간데이터를 문자열로 추출한 다음 UI에 표시합니다. 그리고 setInterval()을 사용하여 초당 한 번 함수(displayTime)를 실행하면 초당 한 번 업데이트되는 디지털 시계와 같은 효과를 만들어냅니다.(see this live, and also see the source): 

- -
function displayTime() {
-   let date = new Date();
-   let time = date.toLocaleTimeString();
-   document.getElementById('demo').textContent = time;
-}
-
-const createClock = setInterval(displayTime, 1000);
- -

setTimeout()과 같이 setInterval()도 식별자 값을 리턴하여 나중에 interval을 취소해야 할 때 사용한다.

- -

interval 취소

- -

setInterval ()은 아무 조치를 취하지 않으면 끊임없이 계속 실행됩니다. 이 상태를 중지하는 방법이 필요합니다. 그렇지 않으면 브라우저가 추가 작업을 완료 할 수 없거나, 현재 처리 중인 애니메이션이 완료되었을 때 오류가 발생할 수 있습니다. setTimeout()과 같은 방식으로 setInterval () 호출에 의해 반환 된 식별자를 clearInterval () 함수에 전달하여 이 작업을 취소할수 있습니다.

- -
const myInterval = setInterval(myFunction, 2000);
-
-clearInterval(myInterval);
- -

능동학습 : 자신만의 스톱워치를 만들기.

- -

위에서 모두 설명해 드렸으니, 과제를 드리겠습니다. setInterval-clock.html 예제를 수정하여 자신만의 간단한 스톱워치를 만들어 보세요.

- -

위에서 설명(디지털 시계)한 것처럼 시간을 표시해야 하고, 이 예제에서는 아래 기능을 추가하세요.

- - - -

몇가지 힌트를 드립니다.

- - - -
-

Note: If you get stuck, you can find our version here (see the source code also).

-
- -

setTimeout()과 setInterval()에서 주의해야할 것 들

- -

setTimeout()과 setInterval()에는 몇가지 주의해야 할 것들이 있습니다. 어떤 것인지 한번 살펴보겠습니다.

- -

순환 timeouts

- -

setTimeout()을 사용하는 또 다른 방법입니다. 바로 setInterval()을 사용하는 대신 setTimeout()을 이용해 같은코드를 반복적으로 실행시키는 방법입니다.

- -

The below example uses a recursive setTimeout() to run the passed function every 100 milliseconds: 아래 예제에서는 setTimeout()이 주어진 함수를 100밀리세컨드마다 실행합니다.

- -
let i = 1;
-
-setTimeout(function run() {
-  console.log(i);
-  i++;
-  setTimeout(run, 100);
-}, 100);
- -

위 예제를 아래 예제와 비교해 보세요. 아래 예제는 setInterval()을 사용하여 같은 결과를 얻을 수 있습니다.

- -
let i = 1;
-
-setInterval(function run() {
-  console.log(i);
-  i++
-}, 100);
- -

그렇다면 순환 setTimeout()과 setInterval()은 어떻게 다를까요?

- -

두 방법의 차이는 미묘합니다.

- - - -

코드가 지정한 시간 간격보다 실행 시간이 오래 걸리면 순환 setTimeout ()을 사용하는 것이 좋습니다. 이렇게하면 코드 실행 시간에 관계없이 실행 간격이 일정하게 유지되어 오류가 발생하지 않습니다.

- -

즉시 timeouts

- -

setTimeout()의 값으로 0을 사용하면 메인 코드 스레드가 실행된 후에 가능한 한 빨리 지정된 콜백 함수의 실행을 예약할 수 있다.

- -

예를 들어 아래 코드 (see it live) 는 "Hello"가 포함된 alert를 출력 한 다음 첫 번째 경고에서 OK를 클릭하자마자 "World"가 포함된 alert를 출력합니다.

- -
setTimeout(function() {
-  alert('World');
-}, 0);
-
-alert('Hello');
- -

이것은 모든 메인 스레드의 실행이 완료되자마자 실행되도록 코드 블록을 설정하려는 경우 유용할 할 수 있습니다. 비동기 이벤트 루프에 배치하면 곧바로 실행될 겁니다.

- -

clearTimeout() 와 clearInterval()의 취소기능

- -

clearTimeout ()과 clearInterval ()은 모두 동일한 entry를 사용하여 대상 메소드(setTimeout () 또는 setInterval ())을 취소합니다. 흥미롭게도 이는 setTimeout () 또는 setInterval ()을 지우는 데 clearTimeout ()과 clearInterval ()메소드 어느 것을 사용해도 무방합니다.

- -

그러나 일관성을 유지하려면 clearTimeout ()을 사용하여 setTimeout () 항목을 지우고 clearInterval ()을 사용하여 setInterval () 항목을 지우십시오. 혼란을 피하는 데 도움이됩니다.

- -

requestAnimationFrame()

- -

requestAnimationFrame ()은 브라우저에서 애니메이션을 효율적으로 실행하기 위해 만들어진 특수한 반복 함수입니다. 근본적으로 setInterval ()의 최신 버전입니다. 브라우저가 다음에 디스플레이를 다시 표시하기 전에 지정된 코드 블록을 실행하여 애니메이션이 실행되는 환경에 관계없이 적절한 프레임 속도로 실행될 수 있도록합니다.

- -

setInterval ()을 사용함에 있어 알려진 문제점을 개선하기위해 만들어졌습니다. 예를 들어 장치에 최적화 된 프레임 속도로 실행되지 않는 문제,  때로는 프레임을 빠뜨리는 문제,  탭이 활성 탭이 아니거나 애니메이션이 페이지를 벗어난 경우에도 계속 실행되는 문제 등등이다 . CreativeJS에서 이에 대해 자세히 알아보십시오.

- -
-

Note: requestAnimationFrame() 사용에 관한 예제들은 이 코스의 여러곳에서 찾아볼 수 있습니다. Drawing graphics 와 Object building practice 의 예제를 찾아 보세요.

-
- -

이 메소드는 화면을 다시 표시하기 전에 호출 할 콜백을 인수로 사용합니다. 이것이 일반적인 패턴입니다. 아래는 사용예를 보여줍니다.

- -
function draw() {
-   // Drawing code goes here
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

그 발상은 애니메이션을 업데이트하는 함수 (예 : 스프라이트 이동, 스코어 업데이트, 데이터 새로 고침 등)를 정의한 후 그것을 호출하여 프로세스를 시작하는 것입니다. 함수 블록의 끝에서 매개 변수로 전달 된 함수 참조를 사용하여 requestAnimationFrame ()을 호출하면 브라우저가 다음 화면을 재표시할 때 함수를 다시 호출하도록 지시합니다. 그런 다음 requestAnimationFrame ()을 반복적으로 호출하므로 계속 실행되는 것입니다.

- -
-

Note: 어떤 간단한  DOM 애니메이션을 수행하려는 경우,   CSS Animations 은 JavaScript가 아닌 브라우저의 내부 코드로 직접 계산되므로 속도가 더 빠릅니다. 그러나 더 복잡한 작업을 수행하고 DOM 내에서 직접 액세스 할 수 없는 객체(예 :2D Canvas API or WebGL objects)를 포함하는 경우 대부분의 경우 requestAnimationFrame ()이 더 나은 옵션입니다. 

-
- -

여러분의 애니메이션의 작동속도는 얼마나 빠른가요?

- -

부드러운 애니메이션을 구현은 직접적으로 프레임 속도에 달려 있으며, 프레임속도는 초당 프레임 (fps)으로 측정됩니다. 이 숫자가 높을수록 애니메이션이 더 매끄럽게 보입니다.

- -

일반적으로 화면 재생률는 60Hz이므로 웹 브라우저를 사용할 때 여러분이 설정할 수 있는 가장 빠른 프레임 속도는 초당 60 프레임 (FPS)입니다. 이 속도보다 빠르게 설정하면 과도한 연산이 실행되어 화면이 더듬거리고 띄엄띄엄 표시될 수 있다. 이런 현상을 프레임 손실 또는 쟁크라고 한다. 

- -

재생률 60Hz의 모니터에 60FPS를 달성하려는 경우 각 프레임을 렌더링하기 위해 애니메이션 코드를 실행하려면 약 16.7ms(1000/60)가 필요합니다. 그러므로 각 애니메이션 루프를 통과 할 때마다 실행하려고 하는 코드의 양을 염두에 두어야합니다.

- -

requestAnimationFrame()은 불가능한 경우에도 가능한한 60FPS 값에 가까워 지려고 노력합니다. 실제로 복잡한 애니메이션을 느린 컴퓨터에서 실행하는 경우 프레임 속도가 떨어집니다. requestAnimationFrame ()은 항상 사용 가능한 것을 최대한 활용합니다.

- -

requestAnimationFrame()이 setInterval(), setTimeout()과 다른점은?

- -

requestAnimationFrame () 메소드가 이전에 살펴본 다른 메소드와 어떻게 다른지에 대해 조금 더 이야기하겠습니다. 위의 코드를 다시 살펴보면;

- -
function draw() {
-   // Drawing code goes here
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

setInterval()을 사용하여 위와 같은 작업을 하는 방법을 살펴봅시다.

- -
function draw() {
-   // Drawing code goes here
-}
-
-setInterval(draw, 17);
- -

앞에서언급했듯이 requestAnimationFrame ()은 시간 간격을 지정하지 않습니다. requestAnimationFrame ()은 현재 상황에서 최대한 빠르고 원활하게 실행됩니다. 어떤 이유로 애니메이션이 화면에 표시되지 않으면 브라우저는 그 애니메이션을 실행하는 데 시간을 낭비하지 않습니다.

- -

반면에 setInterval ()은 특정 시간간격을 필요로 합니다. 1000 ms/60Hz 계산을 통해 최종값 16.6에 도달 한 후 반올림(17)했습니다. 이때 반올림하는 것이 좋습니다. 그 이유는 반내림(16)을하면 60fps보다 빠르게 애니메이션을 실행하려고 하게 되지만 애니메이션의 부드러움에 아무런 영향을 미치지 않기 때문입니다. 앞에서 언급했듯이 60Hz가 표준 재생률입니다.

- -

timestamp를 포함하기

- -

requestAnimationFrame() 함수에 전달 된 실제 콜백에는 requestAnimationFrame()이 실행되기 시작한 이후의 시간을 나타내는 timestamp를  매개변수로 제공할 수 있습니다. 장치 속도에 관계없이 특정 시간과 일정한 속도로 작업을 수행할 수 있으므로 유용합니다. 사용하는 일반적인 패턴은 다음과 같습니다.

- -
let startTime = null;
-
-function draw(timestamp) {
-    if(!startTime) {
-      startTime = timestamp;
-    }
-
-   currentTime = timestamp - startTime;
-
-   // Do something based on current time
-
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

브라우저 지원

- -

requestAnimationFrame()은 setInterval() / setTimeout()보다 좀 더 최신 브라우저에서 지원됩니다. 가장 흥미롭게도 Internet Explorer 10 이상에서 사용할 수 있습니다. 따라서 별도의 코드로 이전 버전의 IE를 지원해야할 필요가 없다면, requestAnimationFrame()을 사용하지 않을 이유가 없습니다.

- -

간단한 예

- -

지금까지 이론적으로는 충분히 살펴보았습니다. 그러면 직접 requestAnimationFrame() 예제를 작성해 봅시다. 우리는 간단한 "스피너 애니메이션"을 만들 것입니다. 여러분들이 앱 사용중 서버 과부하일 때 이것을 자주 보았을 겁니다.

- -
-

Note: 실제로는 CSS 애니메이션을 사용하여 이러한 종류의 간단한 애니메이션을 실행해야 합니다. 그러나 이러한 종류의 예제는 requestAnimationFrame() 사용법을 보여주는 데 매우 유용하며, 각 프레임에서 게임의 디스플레이를 업데이트하는 것과 같이 좀 더 복잡한 작업을 수행 할 때 이러한 종류의 기술을 사용하는 것이 좋습니다.

-
- -
    -
  1. -

    먼저 HTML 템플릿을 여기에서.가져옵니다.

    -
  2. -
  3. -

    <body>안에 빈 <div> 요소를 삽입합니다. 그리고  ↻캐릭터를 그 안에 추가합니다. 이 예제에서 이 원형 화살표가 회전하게됩니다.

    -
  4. -
  5. -

    아래 CSS를 HTML 템플릿에 여러분이 원하는 방식으로 적용하세요. 이 CSS는 페이지 배경을 빨간색으로, <body>의 height를 html height의 100%로 설정합니다. 그리고 <div>를 수직, 수평으로 <body> 중앙에 위치 시킵니다.

    - -
    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;
    -}
    -
  6. -
  7. -

    </body> 태크 위에 <script>를 추가하세요.

    -
  8. -
  9. -

    <script> 안에 아래 자바스크립트 코드를 추가하세요. 여기에서 <div>의 참조를 상수로 저장하고, rotateCount 변수를 0으로 설정하고, 나중에 requestAnimationFrame()의 시작 시간을 저장할 startTime 변수를 null로 설정하고, 그리고 requestAnimationFrame() 콜의 참조를 저장할 초기화하지 않은 rAF 변수를 선언합니다.

    - -
    const spinner = document.querySelector('div');
    -let rotateCount = 0;
    -let startTime = null;
    -let rAF;
    -
    -
  10. -
  11. -

    그 아래에 draw() 함수를 추가합니다. 이 함수는 timestamp 매개변수를 포함하는 애니메이션 코드 작성에 사용됩니다.

    - -
    function draw(timestamp) {
    -
    -}
    -
  12. -
  13. -

    draw() 함수안데 다음 코드를 추가합니다. if 조건문으로 startTime이 정의되지 않았다면 startTime을 정의합니다 (루프 반복의 첫번째에만 작동합니다).  그리고 스피너를 회전시키는 rotateCount 변수값을 설정합니다(현재 timestamp는 시작 timestamp를 3으로 나눈 것이라서 그리 빠르지 않습니다).

    - -
      if (!startTime) {
    -   startTime = timestamp;
    -  }
    -
    -  rotateCount = (timestamp - startTime) / 3;
    -
    -
  14. -
  15. -

    draw() 함수 안의 이전 코드 아래에 다음 블록을 추가합니다. 이렇게하면 rotateCount 값이 359보다 큰지 확인합니다 (예 : 360, 완전한 원). 그렇다면 값을 모듈러 360 (즉, 값을 360으로 나눌 때 남은 나머지)으로 설정하여 원 애니메이션이 합리적인 낮은 값으로 중단없이 계속 될 수 있습니다. 꼭 이렇게 해야되는 것은 아니지만 "128000도" 같은 값보다는 0~359 도의 값으로 작업하는 것이 더 쉽습니다.

    - -
    if (rotateCount > 359) {
    -  rotateCount %= 360;
    -}
    -
  16. -
  17. 다음으로 아래 코드를 추가하세요. 실제 스피너를 회전시키는 코드입니다. -
    spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    -
  18. -
  19. -

     draw() 함수 제일 아래에 다음 코드를 추가합니다. 이 코드는 모든 작업의 키 포인트입니다. draw() 함수를 매개변수로 가져오는 requestAnimationFrame() 콜을 저장하기 위해 앞에서 정의한  rAF 변수를 설정합니다. 이 코드는 애니메이션을 실행시키고, 가능한한 60fps에 근사하게 draw() 함수를 계속 실행합니다.

    - -
    rAF = requestAnimationFrame(draw);
    -
  20. -
- -
-

Note: You can find this example live on GitHub (see the source code also).

-
- -

requestAnimationFrame() call의 취소

- -

cancelAnimationFrame() 메소드를 호출하여 requestAnimationFrame()을 취소할 수 있다. (접두어가 "clear"가 아니고 "cancel"임에 유의) requestAnimationFrame()에 의해서 리턴된 rAF 변수값을 전달받아 취소한다. 

- -
cancelAnimationFrame(rAF);
- -

Active learning: Starting and stopping our spinner

- -

In this exercise, we'd like you to test out the cancelAnimationFrame() 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.

- -

Some hints:

- - - -
-

Note: Try this yourself first; if you get really stuck, check out of our live example and source code.

-
- -

Throttling a requestAnimationFrame() animation

- -

One limitation of requestAnimationFrame() 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?

- -

This was a problem for example in the Monkey Island-inspired walking animation from our Drawing Graphics article:

- -

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

- -

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 requestAnimationFrame(), 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:

- -
if (posX % 13 === 0) {
-  if (sprite === 5) {
-    sprite = 0;
-  } else {
-    sprite++;
-  }
-}
- -

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 posX (character's position on the screen) by two each frame:

- -
if(posX > width/2) {
-  newStartPos = -((width/2) + 102);
-  posX = Math.ceil(newStartPos / 13) * 13;
-  console.log(posX);
-} else {
-  posX += 2;
-}
- -

This is the code that works out how to update the position in each animation frame.

- -

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 rotateCount by one on each frame instead of two.

- -

Active learning: a reaction game

- -

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 A key, and the other with the L key.

- -

When the Start 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.

- -

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

- -

Let's work through this.

- -
    -
  1. -

    First of all, download the starter file for the app — 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.

    -
  2. -
  3. -

    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:

    - -
    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');
    - -

    In order, these are:

    - -
      -
    1. A reference to our spinner, so we can animate it.
    2. -
    3. A reference to the {{htmlelement("div")}} element that contains the spinner, used for showing and hiding it.
    4. -
    5. A rotate count — how much we want to show the spinner rotated on each frame of the animation.
    6. -
    7. A null start time — will be populated with a start time when the spinner starts spinning.
    8. -
    9. An uninitialized variable to later store the {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} call that animates the spinner.
    10. -
    11. A reference to the Start button.
    12. -
    13. A reference to the results paragraph.
    14. -
    -
  4. -
  5. -

    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.

    - -
    function random(min,max) {
    -  var num = Math.floor(Math.random()*(max-min)) + min;
    -  return num;
    -}
    -
  6. -
  7. -

    Next add in the draw() function, which animates the spinner. This is very similar to the version seen in the simple spinner example we looked at earlier:

    - -
    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);
    -}
    -
  8. -
  9. -

    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 display: none;.

    - -
    result.style.display = 'none';
    -spinnerContainer.style.display = 'none';
    -
  10. -
  11. -

    We'll also define a reset() 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:

    - -
    function reset() {
    -  btn.style.display = 'block';
    -  result.textContent = '';
    -  result.style.display = 'none';
    -}
    -
  12. -
  13. -

    OK, enough preparation.  Let's make the game playable! Add the following block to your code. The start() function calls draw() to start the spinner spinning and display it in the UI, hides the Start button so we can't mess up the game by starting it multiple times concurrently, and runs a setTimeout() call that runs a setEndgame() function after a random interval between 5 and 10 seconds has passed. We also add an event listener to our button to run the start() function when it is clicked.

    - -
    btn.addEventListener('click', start);
    -
    -function start() {
    -  draw();
    -  spinnerContainer.style.display = 'block';
    -  btn.style.display = 'none';
    -  setTimeout(setEndgame, random(5000,10000));
    -}
    - -
    -

    Note: You'll see that in this example we are calling setTimeout() without storing the return value (so not let myTimeout = setTimeout(functionName, interval)). 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.

    -
    - -

    The net result of the previous code is that when the Start 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 setEndgame() function, which we should define next.

    -
  14. -
  15. -

    So add the following function to your code next:

    - -
    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);
    -  };
    -}
    - -

    Stepping through this:

    - -
      -
    1. 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.
    2. -
    3. 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.
    4. -
    5. We then attach a keydown event listener to our document — when any button is pressed down, the keyHandler() function is run.
    6. -
    7. Inside keyHandler(), we include the event object as a parameter (represented by e) — 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.
    8. -
    9. We first log e.key to the console, which is a useful way of finding out the key value of different keys you are pressing.
    10. -
    11. When e.key is "a", we display a message to say that Player 1 won, and when e.key 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 Shift), it is counted as a different key.
    12. -
    13. Regardless of which one of the player control keys was pressed, we remove the keydown 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 setTimeout() to call reset() 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.
    14. -
    -
  16. -
- -

That's it, you're all done.

- -
-

Note: If you get stuck, check out our version of the reaction game (see the source code also).

-
- -

Conclusion

- -

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.

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

- -

In this module

- - -- cgit v1.2.3-54-g00ecf