diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
commit | 074785cea106179cb3305637055ab0a009ca74f2 (patch) | |
tree | e6ae371cccd642aa2b67f39752a2cdf1fd4eb040 /files/ru/learn/javascript/asynchronous | |
parent | da78a9e329e272dedb2400b79a3bdeebff387d47 (diff) | |
download | translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.gz translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.bz2 translated-content-074785cea106179cb3305637055ab0a009ca74f2.zip |
initial commit
Diffstat (limited to 'files/ru/learn/javascript/asynchronous')
5 files changed, 1560 insertions, 0 deletions
diff --git a/files/ru/learn/javascript/asynchronous/async_await/index.html b/files/ru/learn/javascript/asynchronous/async_await/index.html new file mode 100644 index 0000000000..751e08b45e --- /dev/null +++ b/files/ru/learn/javascript/asynchronous/async_await/index.html @@ -0,0 +1,411 @@ +--- +title: Making asynchronous programming easier with async and await +slug: Learn/JavaScript/Asynchronous/Async_await +translation_of: 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">В ECMAScript версии 2017 появились <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> (<a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next support in Mozilla</a>). По существу, такие функции есть синтаксический сахар над Promises и Generator functions (<a href="https://tc39.es/ecmascript-asyncawait/">ts39</a>). С их помощью легче писать/читать асинхронный код, ведь они позволяют использовать привычный синхронный стиль написания. В этой статье мы на базовом уровне разберемся в их устройстве.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Примечания:</th> + <td>Чтобы лучше понять материал, желательно перед чтением ознакомиться с основами JavaScript, асинхронными операциями вообще и объектами Promises.</td> + </tr> + <tr> + <th scope="row">Цель материала:</th> + <td>Научить писать современный асинхронный код с использованием Promises и async functions.</td> + </tr> + </tbody> +</table> + +<h2 id="Основы_asyncawait">Основы async/await</h2> + +<h3 id="Ключевое_слово_async">Ключевое слово async</h3> + +<p>Ключевое слово async позволяет сделать из обычной функции (function declaration или function expression) асинхронную функцию (<a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async function</a>). Такая функция делает две вещи:<br> + - Оборачивает возвращаемое значение в Promise<br> + - Позволяет использовать ключевое слово await (см. дальше)</p> + +<p>Попробуйте выполнить в консоли браузера следующий код:</p> + +<pre class="brush: js notranslate">function hello() { return "Hello" }; +hello();</pre> + +<p>Функция возвращает "Hello" — ничего необычного, верно ?</p> + +<p>Но что если мы сделаем ее асинхронной ? Проверим:</p> + +<pre class="brush: js notranslate">async function hello() { return "Hello" }; +hello();</pre> + +<p>Как было сказано ранее, вызов асинхронной функции возвращает объект 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>Чтобы получить значение, которое возвращает 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>Итак, ключевое слово <code>async</code>, превращает обычную функцию в асинхронную и результат вызова функции оборачивает в Promise. Также асинхронная функция позволяет использовать в своем теле ключевое слово await, о котором далее.</p> + +<h3 id="Ключевое_слово_await">Ключевое слово await</h3> + +<p>Асинхронные функции становятся по настоящему мощными, когда вы используете ключевое слово <a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a> — по факту, <strong><code>await</code> работает только в асинхронных функциях</strong>. Мы можем использовать await перед promise-based функцией, чтобы остановить поток выполнения и дождаться результата ее выполнения (результат Promise). В то же время, остальной код нашего приложения не блокируется и продолжает работать.</p> + +<p>Вы можете использовать <code>await</code> перед любой функцией, что возвращает Promise, включая Browser API функции.</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="Переписываем_Promises_с_ипользованием_asyncawait">Переписываем Promises с ипользованием async/await</h2> + +<p>Давайте посмотрим на пример из предыдущей статьи:</p> + +<pre class="brush: js notranslate">fetch('coffee.jpg') +.then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } else { + return 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>К этому моменту вы должны понимать как работают Promises, чтобы понять все остальное. Давайте перепишем код используя async/await и оценим разницу.</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}`); + } else { + 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, мы можем использовать гибридный подход:</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}`); + } else { + 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">live example</a> (а также <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await.html">source code</a>).</p> + +<h3 id="Минуточку_а_как_это_все_работает">Минуточку, а как это все работает ?</h3> + +<p>Вы могли заметить, что мы обернули наш код в функцию и сделали ее асинхронной с помощью acync. Это было обязательно - нам надо создать контейнер, внутри которого будет запускаться асинхронный код и будет возмоность дождаться его результата с помощью await, не блокируя остальной код нашег скрипта.</p> + +<p>Внутри <code>myFetch()</code> находится код, который слегка напоминает версию на Promise, но есть важные отличия. Вместо того, чтобы писать цепочку блоков <code>.then()</code> мы просто использует ключевое слово <code>await</code> перед вызовом promise-based функции и присваиваем результат в переменную. Ключеовое слово <code>await</code> говорит JavaScript runtime приостановить код в этой строке, не блокируя остальной код скприта за пределами асинхронной функции. Когда вызов promise-based функции будет готов вернуть результат, выполнение продолжится с этой строки дальше.<br> + <br> + Пример:</p> + +<pre class="brush: js notranslate">let response = await fetch('coffee.jpg');</pre> + +<p>Значение Promise, которое вернет <code>fetch()</code> будет присвоено переменной <code>response</code> только тогда, когда оно будет доступно - парсер делает паузу на данной строке дожидаясь этого момента. Как только значение доступно, парсер переходит к следующей строке, в которой создается объект <code><a href="/en-US/docs/Web/API/Blob">Blob</a></code> из результата Promise. В этой строке, кстати, также используется <code>await</code>, потому что метод <code>.blob()</code> также возвращет Promise. Когда результат готов, мы возвращаем его наружу из <code>myFetch()</code>.</p> + +<p>Обратите внимание, когда мы вызываем <code>myFetch()</code>, она возвращает Promise, поэтому мы можем вызвать <code>.then()</code> на результате, чтобы отобразить его на экране.<br> + <br> + К этому моменту вы наверное думаете "Это реально круто!", и вы правы - чем меньше блоков <code>.then()</code>, тем легче читать код.</p> + +<h3 id="Добавляем_обработку_ошибок">Добавляем обработку ошибок</h3> + +<p><br> + Чтобы обработать ошибки у нас есть несколько вариантов</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}`); + } else { + 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}`); + } else { + 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> словит ошибки как из асинхронной функции, так и из Promise. Если бы мы использовали блок <code>try</code>/<code>catch</code>, мы бы не словили ошибку, которая произошла в самой <code>myFetch()</code> функции.</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">source code</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">source code</a>)</li> +</ul> + +<h2 id="Await_и_Promise.all">Await и Promise.all()</h2> + +<p>Как вы помните, асинхронные функции построены поверх <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a>, поэтому они совместимы со всеми возможностями последних. Мы легко можем подождать выполнение <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all()</a></code>, присвоить результат в переменную и все это сделать используя синхронный стиль. Опять, вернемся к <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">an example we saw in our previous article</a>. Откройте пример в соседней вкладке, чтобы лучше понять разницу.</p> + +<p>Версия с async/await (смотрите <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-all-async-await.html">live demo</a> и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-all-async-await.html">source code</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> мы ждем массив результатов всех трех Promises и присваиваем его в переменную <code>values</code>. Это асинхронный код, но он написан в синхронном стиле, за счет чего он гораздо читабельнее.<br> + <br> + Мы должны обернуть весь код в синхронную функцию, <code>displayContent()</code>, и мы не сильно сэкономили на количестве кода, но мы извлекли код блока <code>.then()</code>, за счет чего наш код стал гораздо чище.</p> + +<p>Для обработки ошибок мы добавили блок <code>.catch()</code> для функции <code>displayContent()</code>; Это позволило нам отловить ошибки в обоих функциях.</p> + +<div class="blockIndicator note"> +<p><strong>Заметка</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/Statements/try...catch#The_finally_clause">.finally</a>()</code>, чтобы получить информацию о результате нашей операции — смотрите в действии в нашем <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-finally-async-await.html">live example</a> (смотрите <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-finally-async-await.html">source code</a>).</p> +</div> + +<h2 id="Недостатки_asyncawait">Недостатки async/await</h2> + +<p>Асинхронные функции с async/await бывают очень удобными, но есть несколько замечаний, о которых полезно знать.</p> + +<p>Async/await позволяет вам писать код в синхронном стиле. Ключевое слово <code>await</code> блокирует приостанавливает выполнение ptomise-based функции до того момента, пока promise примет статуc fulfilled. Это не блокирует код за пределами вашей асинхронной функции, тем не менее важно помнить, что внутри асинхронной функции поток выполнения блокируется.<br> + <br> + Ваш код может стать медленнее за счет большого количества awaited promises, которые идут один за другим. Каждый <code>await</code> должен дождаться выполнения предыдущего, тогда как на самом деле мы хотим, чтобы наши Promises выполнялись одновременно, как если бы мы не использовали async/await.<br> + <br> + Есть подход, который позволяет обойти эту проблему - сохранить все выполняющиеся Promises в переменные, а уже после этого дожидаться (awaiting) их результата. Давайте посмотрим на несколько примеров.</p> + +<p>We've got two examples available — <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/slow-async-await.html">slow-async-await.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/slow-async-await.html">source code</a>) and <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/fast-async-await.html">fast-async-await.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/fast-async-await.html">source code</a>). Both of them start off with a custom promise function that fakes an async process with a <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code> call:</p> + +<pre class="brush: js notranslate">function timeoutPromise(interval) { + return new Promise((resolve, reject) => { + setTimeout(function(){ + resolve("done"); + }, interval); + }); +};</pre> + +<p>Then each one includes a <code>timeTest()</code> async function that awaits three <code>timeoutPromise()</code> calls:</p> + +<pre class="brush: js notranslate">async function timeTest() { + ... +}</pre> + +<p>Each one ends by recording a start time, seeing how long the <code>timeTest()</code> promise takes to fulfill, then recording an end time and reporting how long the operation took in total:</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>It is the <code>timeTest()</code> function that differs in each case.</p> + +<p>In the <code>slow-async-await.html</code> example, <code>timeTest()</code> looks like this:</p> + +<pre class="brush: js notranslate">async function timeTest() { + await timeoutPromise(3000); + await timeoutPromise(3000); + await timeoutPromise(3000); +}</pre> + +<p>Here we simply await all three <code>timeoutPromise()</code> calls directly, making each one alert for 3 seconds. Each subsequent one is forced to wait until the last one finished — if you run the first example, you'll see the alert box reporting a total run time of around 9 seconds.</p> + +<p>In the <code>fast-async-await.html</code> example, <code>timeTest()</code> looks like this:</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>Here we store the three <code>Promise</code> objects in variables, which has the effect of setting off their associated processes all running simultaneously.</p> + +<p>Next, we await their results — because the promises all started processing at essentially the same time, the promises will all fulfill at the same time; when you run the second example, you'll see the alert box reporting a total run time of just over 3 seconds!</p> + +<p>You'll have to test your code carefully, and bear this in mind if performance starts to suffer.</p> + +<p>Another minor inconvenience is that you have to wrap your awaited promises inside an async function.</p> + +<h2 id="Asyncawait_class_methods">Async/await class methods</h2> + +<p>As a final note before we move on, you can even add <code>async</code> in front of class/object methods to make them return promises, and <code>await</code> promises inside them. Take a look at the <a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance#ECMAScript_2015_Classes">ES class code we saw in our object-oriented JavaScript article</a>, and then look at our modified version with an <code>async</code> method:</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>The first class method could now be used something like this:</p> + +<pre class="brush: js notranslate">han.greeting().then(console.log);</pre> + +<h2 id="Browser_support">Browser support</h2> + +<p>One consideration when deciding whether to use async/await is support for older browsers. They are available in modern versions of most browsers, the same as promises; the main support problems come with Internet Explorer and Opera Mini.</p> + +<p>If you want to use async/await but are concerned about older browser support, you could consider using the <a href="https://babeljs.io/">BabelJS</a> library — this allows you to write your applications using the latest JavaScript and let Babel figure out what changes if any are needed for your user’s browsers. On encountering a browser that does not support async/await, Babel's polyfill can automatically provide fallbacks that work in older browsers.</p> + +<h2 id="Conclusion">Conclusion</h2> + +<p>And there you have it — async/await provide a nice, simplified way to write async code that is simpler to read and maintain. Even with browser support being more limited than other async code mechanisms at the time of writing, it is well worth learning and considering for use, both for now and in the future.</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "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/ru/learn/javascript/asynchronous/concepts/index.html b/files/ru/learn/javascript/asynchronous/concepts/index.html new file mode 100644 index 0000000000..b82d4d4a51 --- /dev/null +++ b/files/ru/learn/javascript/asynchronous/concepts/index.html @@ -0,0 +1,164 @@ +--- +title: Основные понятия асинхронного программирования +slug: Learn/JavaScript/Asynchronous/Concepts +tags: + - JavaScript + - Promises + - Асинхронность + - Обучение + - блокировки + - потоки +translation_of: Learn/JavaScript/Asynchronous/Concepts +--- +<div>{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}</div> + +<p>В этой статье мы бегло познакомимся с основными понятиями, связанными с асинхронным программированием и как они применяются в веб браузерах и JavaScript. Вы должны понять эти концепции, прежде чем приступать к другим статьям этого раздела.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые знания:</th> + <td>Базовая компьютерная грамотность, знакомство с основами JavaScript.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td><span class="tlid-translation translation" lang="ru"><span title="">Понять основные идеи асинхронного программирования, и как они проявляются в веб-браузерах и JavaScript.</span></span></td> + </tr> + </tbody> +</table> + +<h2 id="Что_же_такое_Асинхронность">Что же такое Асинхронность?</h2> + +<p>Как правило, программный код выполняется последовательно, только одна конкретная операция происходит в данный момент времени. Если функция зависит от результата выполнения другой функции, то она должна дождаться пока нужная ей функция не завершит свою работу и не вернет результат и до тех пор пока это не произойдет, выполнение программы, по сути, будет остановлено с точки зрения пользователя.</p> + +<p>Пользователь современного ПК, наверняка, наблюдал, как курсор меняет свой вид и становится "разноцветным спинером" (у пользователей MacOS). Таким образом операционная система сообщает - "текущая программа, ожидает завершения какого то длительного процесса в системе и я решила сообщить тебе, что бы ты не волновался".</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>Такое поведение удручает и говорит о неправильном использовании процессорного времени, к тому же современные компьютеры имеют процессоры с несколькими ядрами. Не нужно ничего ждать, вы можете передать следующую задачу свободному ядру процессора и когда она завершится, то сообщит вам об этом. Такой подход позволяет выполнять разные задачи одновременно, в этом и заключается задача асинхронности в программировании. Программная среда, которую вы используете (браузер в случае веб разработки), должна иметь возможность выполнять различного рода задачи ассинхронно.</p> + +<h2 id="Блокировка_кода">Блокировка кода</h2> + +<p>Асинхронные техники очень полезны, особенно при веб разработке. Когда ваше приложение запущено в браузере и выполняет свои задачи, не возвращая контроль окружению, браузер может подвисать. Это называется <strong>блокировка</strong>; браузер заблокирован и не может реагировать на действия пользователя и выполнять служебные.задачи, до тех пор пока веб приложение не освободит ресурсы процессора.</p> + +<p>Давайте рассмотрим несколько примеров, которые покажут, что именно значит <strong>блокировка</strong>.</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>), добавим кнопке событие на клик, чтобы при нажатии на нее запускалась трудоемкая операция (рассчет 10000000 дат, и вывод последнейрассчитаной даты на консоль) после чего в DOM добавляется еще один параграф:</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>Примечание</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">посмотреть пример</a>), мы сделаем что-нибудь более реалистичное, с чем вы сможете столкнуться на реальной странице. Мы заблокируем действия пользователя отрисовкой страницы. В этом примере у нас две кнопки:</p> + +<ul> + <li>Кнопка "Fill canvas", если на нее кликнуть, рисует в элементе + <div>{{htmlelement("canvas")}}</div> + миллион синих кругов.</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>Примечание</strong>: Хорошо, в приведенном некрасивом примере, мы получили эффект блокировки, который показывает общую проблему при разработке приложений, с которой все время приходится бороться разработчикам.</p> +</div> + +<p>Почему так происходит? Потому что JavaScript, в общем случае, выполняет команды в <strong>одном потоке</strong>. Пришло время познакомиться с понятием <strong>потока</strong>.</p> + +<h2 id="Потоки">Потоки</h2> + +<p>Под <strong>потоком,</strong> обычно, понимают одиночный процесс, который может использовать программа, для выполнения своих нужд. Каждый поток может выполнять только одну в текущий момент времени:</p> + +<pre class="notranslate">Task A --> Task B --> Task C</pre> + +<p>Каждая задача будет выполнена последовательно; только когда текущая задача завершится, следующая сможет начаться.</p> + +<p>Как мы говорили выше, большинство компьютеров теперь имеют процессор с несколькими ядрами, т.е. могут выполнять несколько задач одновременно. Языки программирования, поддерживающие многопоточность, могут использовать несколько ядер, чтобы выпонять несколько задач одновременно:</p> + +<pre class="notranslate">Thread 1: Task A --> Task B +Thread 2: Task C --> Task D</pre> + +<h3 id="JavaScript_однопоточный">JavaScript однопоточный</h3> + +<p>JavaScript, традиционно для скриптовых языков, однопоточный. Даже, если есть несколько ядер, вы можете использовать их только для выполнения задач в одном потоке, называемом <strong>основной поток</strong>. Наш пример выше, выполняется следующим образом:</p> + +<pre class="notranslate">Main thread: Render circles to canvas --> Display alert()</pre> + +<p>В итоге, JavaScript получил несколько инструментов, которые могут помочь в решении подобных проблем. <a href="/en-US/docs/Web/API/Web_Workers_API">Web workers</a> позволяют вам обработать некоторый JavaScript код в отдельном потоке, который называется обработчик, таким образом вы можете запускать отдельные блоки JavaScript кода одновременно. В основном, вы будете использовать воркеры, чтобы запустить ресурсоемкий процесс, отдельно от основного потока, чтобы не блокировать действия пользователя.</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">посмотреть пример в действии</a>), с открытой консолью. Это переписанный предыдущий пример, который теперь рассчитывает 10 миллионов дат в отдельном потоке обработчика. Теперь, когда вы нажимаете на кнопку, браузер может добавить новый элемент на страницу, до того как все даты будут посчитаны. Самая первая операция больше не блокирует выполнение следующей.</p> + +<h2 id="Асинхронный_код">Асинхронный код</h2> + +<p>Воркеры полезный инструмент, но у них есть свои ограничения. Самое существенное, заключается в том, что они не имеют доступа к {{Glossary("DOM")}} — вы не можете использовать воркер для обновления UI. Мы не можем отрисовать миллион наших точек внутри воркера; он может только обработать большой объем информации.</p> + +<p>Следующая проблема заключается в том, что даже если код запущенный в воркере ничего не блокирует, он в целом остается синхронным. Это проблема появляется, когда какой-то функции требуются результаты выполнения нескольких предыдущих функций. Рассмотрим следующую диаграмму потоков:</p> + +<pre class="notranslate">Main thread: Task A --> Task B</pre> + +<p>В этом примере, предположим Task A делает что-то вроде получения картинки с сервера а Task B затем делает что-нибудь с полученной картинкой, например, применяет к ней фильтр. Если запустить выполняться Task A и тут же попытаться выполнить Task B, то вы получите ошибку, поскольку картинка еще не будет доступна.</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 D попытаться запустить, когда какого-то нужного ей результата еще нет, выполнение закончится ошибкой.</p> + +<p>Чтобы избежать подобных проблем, браузеры позволяют нам выполнять определенные операции асинхронно. Такие возможности, как <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> позволяют запустить некоторую операцию (например, получение картинки с сервера), и затем подождать пока операция не вернет результат, перед тем как начать выполнение другой задачи:</p> + +<pre class="notranslate">Main thread: Task A Task B + Promise: |__async operation__|</pre> + +<p>Поскольку операция выполняется где-то отдельно, основной поток не блокируется, при выполнении асинхронных задач.</p> + +<p>В следующей статье, мы покажем вам, как писать асинхронный код. Захватывает дух, неправда ли? Продолжайте читать!</p> + +<h2 id="Заключение">Заключение</h2> + +<p>При проектировании современных программ все больше используется асинхронное программирование, чтобы программа имела возможность выполнять несколько операций в конкретный момент времени. Как только вы начнете использовать новые, более мощные возможности API, вы обнаружите множество ситуаций, где решить нужную задачу можно только асинхронно. Раньше было сложно писать асинхронный код. До сих пор, нужно время, чтобы привыкнуть к такому подходу, но процесс стал намного легче. Далее, в этом разделе, мы будем глубже исследовать вопрос, когда же асинхронный код необходим и как спроектировать программу, чтобы избежать проблем, описанных выше.</p> + +<h2 id="В_этом_модуле">В этом модуле</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/ru/learn/javascript/asynchronous/index.html b/files/ru/learn/javascript/asynchronous/index.html new file mode 100644 index 0000000000..19d66b114c --- /dev/null +++ b/files/ru/learn/javascript/asynchronous/index.html @@ -0,0 +1,59 @@ +--- +title: Асинхронный JavaScript +slug: Learn/JavaScript/Asynchronous +tags: + - Beginner + - CodingScripting + - Guide + - JavaScript + - Landing + - Promises + - async + - asynchronous + - await + - callbacks + - requestAnimationFrame + - setInterval + - setTimeout + - Асинхронность + - асинхронное программирование +translation_of: Learn/JavaScript/Asynchronous +--- +<div>{{LearnSidebar}}</div> + +<p class="summary"><span class="seoSummary">В этом модуле мы рассмотрим {{Glossary("asynchronous")}} {{Glossary("JavaScript")}}, почему это важно, и как это поможет эффективно справляться с потенциальной блокировкой операций, таких как получение ресурсов с сервера или запись в файл.</span></p> + +<h2 id="Необходимые_знания">Необходимые знания</h2> + +<p>Асинхронный JavaScript довольно сложная тема, и мы советуем пройти <a href="/ru/docs/Learn/JavaScript/First_steps">Первые шаги в JavaScript</a> и <a href="/ru/docs/Learn/JavaScript/Building_blocks">Блоки в JavaScript</a> прежде чем начать эту тему.</p> + +<p>Если вы ещё не знакомы с концепциями асинхронного программирования, вам стоит начать со статьи <a href="/ru/docs/Learn/JavaScript/Asynchronous/Concepts">Основные концепции асинхронного программирования</a> в этом модуле. А если уже знакомы, то можете сразу переходить к статье <a href="/ru/docs/Learn/JavaScript/Asynchronous/Introducing">Введение в асинхронный JavaScript</a>.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Если вы работаете за компьютером/планшетом/другим устройством где у вас нет возможности создавать собственные файлы, вы можете попробовать(почти все) примеры кода в одном из веб-приложений, таких, как <a href="http://jsbin.com/">JSBin</a> или <a href="https://thimble.mozilla.org/">Thimble</a>.</p> +</div> + +<h2 id="Руководства">Руководства</h2> + +<dl> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Concepts">Основные концепции асинхронного программирования</a></dt> + <dd> + <p>В этой статье мы пройдёмся по нескольким важным концепциям касающихся асинхронного программирования, и того как это выглядит в браузерах и JavaScript. Вы должны усвоить эти концепции прежде чем изучать другие статьи в этом модуле.</p> + </dd> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Introducing">Введение в асинхронный JavaScript</a></dt> + <dd>В этой статье мы кратко расскажем о проблемах связанных с синхронным JavaScript-ом, и взглянем на различные техники асинхронного программирования с которыми вы столкнётесь, покажем вам как эти техники помогают решать проблемы синхронного JavaScript.</dd> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Loops_and_intervals">Кооперативная асинхронность в JavaScript: Таймауты и интервалы</a></dt> + <dd>Здесь мы рассматриваем традиционные методы JavaScript, которые позволяют запускать код асинхронно по истечению заданного времени, или с регулярным интервалом (например: заданное количество раз в секунду), обсудим их пользу, а так же их неотъемлемые проблемы.</dd> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Promises">Изящная обработка асинхронных операций с Промисами</a></dt> + <dd>Промисы это достаточно новая функция в языке JavaScript, которая позволяет вам откладывать дальнейшие операции, пока предыдущая не выполнится, или реагировать на её неудачное выполнение. Это очень полезно, для установки нужной последовательности операций для корректной работы. Эта статья показывает как работают промисы, и вы рассмотрите то, как они работают в WebAPIs, и узнаете как писать свои собственные.</dd> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Async_await">Делаем асинхронное программирование проще с async и await</a></dt> + <dd>Промисы могут быть достаточно сложными для написания и понимания, поэтому современные браузеры ввели функцию <code>async</code> и оператор <code>await</code> — где первый позволяет стандартным функциям неявно асинхронно работать с промисами, а последний может использоваться внутри <code>async</code> функций, для ожидания промиса, прежде чем функция продолжит свою работу, что делает работу с промисами проще и улучшает читабельность кода.</dd> + <dt><a href="/ru/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Выбор правильного подхода</a></dt> + <dd>В завершение этого модуля, мы рассмотрим технологии и функции, которые мы обсуждали, рассмотрим когда и где их надо использовать. А так же дадим рекомендации и расскажем о распространённых подводных камнях, там где это будет необходимо.</dd> +</dl> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://eloquentjavascript.net/11_async.html">Асинхронное программирование</a> из фантастической онлайн книги Марина Хавербеке, <a href="https://karmazzin.gitbooks.io/eloquentjavascript_ru/">Выразительный JavaScript</a>.</li> +</ul> diff --git a/files/ru/learn/javascript/asynchronous/introducing/index.html b/files/ru/learn/javascript/asynchronous/introducing/index.html new file mode 100644 index 0000000000..b25ca4037e --- /dev/null +++ b/files/ru/learn/javascript/asynchronous/introducing/index.html @@ -0,0 +1,288 @@ +--- +title: Введение в асинхронный JavaScript +slug: Learn/JavaScript/Asynchronous/Introducing +tags: + - Асинхронность + - Введение + - Гайд + - Начинающий + - Промисы + - Статья + - колбэк + - чейнить +translation_of: Learn/JavaScript/Asynchronous/Introducing +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}</div> + +<p class="summary">В этой статье мы кратко остановимся на проблемах, связанных с синхронным Javascript, а также ознакомимся с несколькими асинхронными методами, демонстрирующими как они могут помочь нам подобные проблемы решить.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимое условие:</th> + <td>Базовая компьютерная грамотность, достаточное понимание основ JavaScript.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Ознакомиться с тем, что такое асинхронный JavaScript, чем он отличается от синхронного и в каких случаях используется.</td> + </tr> + </tbody> +</table> + +<h2 id="Синхронный_JavaScript">Синхронный JavaScript</h2> + +<p>Чтобы (позволить нам) понять что есть <strong>{{Glossary("асинхронный")}}</strong> JavaScript, нам следовало бы для начала убедиться, что мы понимаем что такое <strong>{{Glossary("синхронный")}}</strong> JavaScript. Этот раздел резюмирует некоторую информацию из прошлой статьи.</p> + +<p>Большая часть функционала, которую мы рассматривали в предыдущих обучающих модулях, является синхронной — вы запускаете какой-то код, а результат возвращается, как только браузер может его вернуть. Давайте рассмотрим простой пример ( посмотрите <a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/basic-function.html">онлайн</a>, как это работает и посмотрите <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/basic-function.html">исходный код</a>):</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); +btn.addEventListener('click', () => { + alert('You clicked me!'); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +}); +</pre> + +<p>В этом блоке кода команды выполняются одна за другой:</p> + +<ol> + <li>Получаем ссылку на элемент {{htmlelement("button")}}, который уже есть в DOM.</li> + <li>Добавляем к кнопке обработчик события <code><a href="/en-US/docs/Web/API/Element/click_event">click</a></code> так, что при нажатии на неё: + <ol> + <li>Выводится сообщение <code><a href="/en-US/docs/Web/API/Window/alert">alert()</a></code>.</li> + <li>После закрытия сообщения создаём элемент {{htmlelement("p")}} (абзац).</li> + <li>Затем добавляем в абзац текст.</li> + <li>В конце добавляем созданный абзац в тело документа.</li> + </ol> + </li> +</ol> + +<p>Пока выполняется каждая операция, ничего больше не может произойти — обработка (отображение) документа приостановлена. Так происходит, как было сказано <a href="/ru/docs/Learn/JavaScript/Asynchronous/Introducing">в предыдущей статье</a>, потому что <a href="/ru/docs/Learn/JavaScript/Asynchronous/Concepts#JavaScript_%D0%BE%D0%B4%D0%BD%D0%BE%D0%BF%D0%BE%D1%82%D0%BE%D1%87%D0%BD%D1%8B%D0%B9">JavaScript является однопоточным</a>. В каждый момент времени может выполняться только одна команда, обрабатываемая в единственном — главном потоке. Все остальные действия блокируются до окончания выполнения текущей команды.</p> + +<p>Так и в примере выше: после нажатия кнопки абзац не сможет появиться пока не будет нажата кнопка OK в окне сообщения. Попробуйте сами:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><<span class="pl-ent">button</span>>Нажми меня</<span class="pl-ent">button</span>></pre> +</div> + +<p>{{EmbedLiveSample('Synchronous_JavaScript', '100%', '70px')}}</p> + +<div class="blockIndicator note"> +<p><strong>Заметка</strong>: Важно помнить, что <code><a href="/en-US/docs/Web/API/Window/alert">alert()</a></code>, хоть и часто используется для демонстрации синхронных блокирующих операций, сильно не рекомендован к использованию в реальных приложениях.</p> +</div> + +<h2 id="Асинхронный_JavaScript">Асинхронный JavaScript</h2> + +<p>По причинам, упомянутым ранее (например, относящимся к блокировке), множество Web API особенностей теперь используют асинхронный код, особенно те,что имеют доступ к внешним устройствам или получают от них некоторые ресурсы, такие как получение файла из сети, запрос к базе данных и получение данных из базы, доступ к потоковому видео на веб-камере, просмотр дисплея на гарнитуре виртуальной реальности.</p> + +<p>Почему трудно работать, используя синхронный код? Давайте посмотрим на небольшой пример. Когда вы получаете картинку с сервера, вы не можете мгновенно вернуть результат. Это значит что следующий (псевдо) код не сработает:</p> + +<pre class="brush: js notranslate">let response = fetch('myImage.png'); +let blob = response.blob(); +// display your image blob in the UI somehow</pre> + +<p>Это проиходит потому что вы не знаете сколько времени займет загрузка картинки, следовательно, когда вы начнёте выполнять вторую строку кода, сгенерируется ошибка (возможно, переодически, возможно, каждый раз), потому что <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">response</span></font> еще не доступен. Вместо этого, ваш код должен дождаться возвращения <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">response</span></font> до того, как попытается выполнить дальнешие инструкции.</p> + +<p>Есть два типа стиля асинхронного кода, с которыми вы столкнетесь в коде JavaScript, старый метод — callbacks (обратные вызовы) и более новый — promise (промисы, обещания). В следующих разделах мы познакомимся с каждым из них. </p> + +<h2 id="Асинхронные_обратные_вызовы">Асинхронные обратные вызовы</h2> + +<p>Асинхронные обратные вызовы — это функции, которые определяются как агрументы при вызове функции, которая начнет выполнение кода на заднем фоне. Когда код на заднем фоне завершает свою работу, он вызвает функцию обратного вызова, оповещающую, что работа сделана, либо оповещающую о трудностях в завершении работы. Обратные вызовы — немного устраревшая практика, но они все еще употребляются в некоторых старомодных, но часто используемых API.</p> + +<p>Пример асинхронного обратного вызова вторым параметром {{domxref("EventTarget.addEventListener", "addEventListener()")}} (как мы видели выше):</p> + +<pre class="brush: js notranslate">btn.addEventListener('click', () => { + alert('You clicked me!'); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +});</pre> + +<p>Первый параметр — тип прослушиваемого события, второй параметр — функция обратного вызова, вызываемая при срабатывании события.</p> + +<p>При передаче функциии обратного вызова как аргумента в другую функцию, мы передаем только ссылку на функцию как аргумент, следовательно коллбэк функция <strong>не</strong> выполняется мгновенно. Где-то существует "обратный вызов" (отсюда и название), выполняющийся асинхронно внутри тела, содержащего функцию. Эта функция должна выполнять функцию обратногго вызова в нужный момент.</p> + +<p>Вы можете написать свою собственную функцию, содержащую функцию обратного вызова. Давайте взглянем на еще один пример, в котором происходит загрузка ресурсов через <a href="/en-US/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code> API</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/xhr-async-callback.html">запустите пример</a>, и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/xhr-async-callback.html">посмотрите исходный код</a>):</p> + +<pre class="brush: js notranslate">function loadAsset(url, type, callback) { + let xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = type; + + xhr.onload = function() { + callback(xhr.response); + }; + + xhr.send(); +} + +function displayImage(blob) { + let objectURL = URL.createObjectURL(blob); + + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +loadAsset('coffee.jpg', 'blob', displayImage);</pre> + +<p>Мы создали функцию <code>displayImage()</code>, которая представляет blob, переданный в нее, как обьект URL, и создает картинку, в которой отображается URL, добавляя ее в элемент документа <code><body></code>. Однако, далее мы создаем функцию <code>loadAsset()</code>, которая принимает функцию обратного вызова в качестве параметра, вместе с URL для получения данных и типом контента. Для получения данных из URL используется <code>XMLHttpRequest</code> (часто сокращается до аббревиатуры "XHR") , перед тем как передать ответ в функцию обратного вызова для дальнейшей обработки. В этом случае функция обратного вызова ждет, пока XHR закончит загрузку данных (используя обрабочик события <code><a href="/en-US/docs/Web/API/XMLHttpRequestEventTarget/onload">onload</a></code>) перед отправкой данных в функцию обратного вызова.</p> + +<p>Функции обратного вызова универсальны — они не только позволяют вам контролировать порядок, в котором запускаются функции и данные, передающиеся между ними, они также позволяют передавать данные различным функциям, в зависимости от обстоятельств. Вы можете выполнять различные действия с загруженным ответом, такие как <code>processJSON()</code>, <code>displayText()</code>, и другие.</p> + +<p>Заметьте, что не все функции обратного вызова асинхронны — некторые запускаются синхронно. Например, при использовании {{jsxref("Array.prototype.forEach()")}} для перебора элементов массива (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/foreach.html">запустите пример</a>, и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/foreach.html">посмотрите исходный код</a>):</p> + +<pre class="brush: js notranslate">const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus']; + +gods.forEach(function (eachName, index){ + console.log(index + '. ' + eachName); +});</pre> + +<p>В этом примере мы перебираем массив с именами греческих богов и выводим индексы и значения в консоль. Ожидаемый параметр для <code>forEach() </code> — это функцияобратного вызова, которая содержит два параметра: ссылку на имя массива и значения индексов. Однако эта функция не ожидает никаких действий — она запускается немедленно.</p> + +<h2 id="Промисы">Промисы</h2> + +<p>Промисы — новый стиль написания асинхронного кода, который используется в современных Web API. Хорошим примером является <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a></code> API, который современнее и эффективнее чем {{domxref("XMLHttpRequest")}}. Посмотрим на краткий пример, из нашей статьи <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a>:</p> + +<pre class="brush: js notranslate">fetch('products.json').then(function(response) { + return response.json(); +}).then(function(json) { + products = json; + initialize(); +}).catch(function(err) { + console.log('Ошибка загрузки: ' + err.message); +});</pre> + +<div class="blockIndicator note"> +<p><strong>Заметка</strong>: Вы можете посмотреть законченную версию на github (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store-xhr/can-script.js">посмотрите исходный код</a> и <a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store-xhr/">запустите пример</a>).</p> +</div> + +<p>В примере видно, как <code>fetch()</code> принимает один параметр — URL ресурса, который нужно получить из сети, — и возвращает <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">промис</a>. Промис или обещание — это объект, представляющий асинхронную операцию, выполенную удачно или неудачно. Он представляет собой как бы промежуточное состояние. По сути, это способ браузера сказать: "я обещаю вернуться к вам с ответом как можно скорее," отсюда и название "обещание."</p> + +<p>Может пнадобиться много времени, чтобы привыкнуть к данной концепуии; это немного напоминает {{interwiki("wikipedia", "Кот Шрёдингера")}} в действии. Ни один из возможных результатов еще не произошел, поэтому операция fetch в настоящее время ожидает результата. Далее у нас есть три блока кода следующих сразу после <code>fetch()</code>:</p> + +<ul> + <li>Два <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then">then()</a></code> блока. Оба включают в себя функцию обратного, которая запустится, если предыдущая операция закончилась успешно, и каждая функция обратного вызова принимает на вход результат предыдущей успешно выполненной операции, таким образом вы можете выполнять операции последовательно. Каждый <code>.then()</code> блок возвращает новый promise, это значит что вы можете объеденять в цепочки (чейнить) блоки <code>.then()</code>, таким образом можно выполнить несколько асинхронных операций по порядку, одну за другой.</li> + <li><code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch">catch()</a></code> блок описывается в конце и будет запущен если какой-либо <code>.then()</code> блок завершится с ошибкой — это аналогично синхронному <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code>, ошибка становится доступной внутри <code>catch()</code>, что может быть использовано для сообщения пользователю о типе возникшей ошибки. Однако синхронный <code>try...catch</code> не будет работать с promise, хотя будет работать с <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">async/await</a>, с которыми вы познакомитесь позже.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Заметка</strong>: Вы узнаете намного больше о promise позже в этом модуле, так что не волнуйтесь если вы что-нибудь не поняли.</p> +</div> + +<h3 id="Очередь_событий">Очередь событий</h3> + +<p>Асинхронные операции, такие как промисы, помещаются в <strong>очередь событий</strong>, которая запускается после завершения обработки основного потока, чтобы они <em>не блокировали</em> выполнение JavaScript кода. Поставленные в очередь операции завершатся как можно скорее, а затем вернут свои результаты в среду JavaScript .</p> + +<h3 id="Промисы_и_функции_обратного_вызова">Промисы и функции обратного вызова</h3> + +<p>Промисы имеют некоторое сходство со старомодными функциями обратного вызова. По сути, они являются возвращаемым объектом, к которому вы присоединяете функции обратного вызова, вместо того, чтобы передавать обратные вызовы в функцию.</p> + +<p>Тем не менее, промисы сделаны специально для обработки асинхронных операций, и имеют много преимуществ по сравнению с обратными вызовами:</p> + +<ul> + <li>Вы можете объединить несколько асинхронных операций вместе, используя несколько операций<code>.then()</code>, передавая результат одного в следующий в качестве входных данных. Это гораздо сложнее сделать с обратными вызовами, которые часто заканчиваются массивным «адом обратных вызовов» (также известным как <a href="http://callbackhell.com/">callback hell</a>).</li> + <li>Обратные вызовы Promise всегда вызываются в строгом порядке, который они помещают в очередь событий..</li> + <li>Обработка ошибок намного лучше — все ошибки обрабатываются одним блоком <code>.catch ()</code> в конце блока, а не обрабатываются индивидуально на каждом уровне «пирамиды».</li> + <li>Промисы избегают инверсии управления, в отличие от обратных вызовов, которые теряют полный контроль над тем, как будет выполняться функция при передаче обратного вызова в стороннюю библиотеку.</li> +</ul> + +<h2 id="Природа_асинхронного_кода">Природа асинхронного кода</h2> + +<p>Давайте рассмотрим пример, который дополнительно иллюстрирует природу асинхронного кода, показывая, что может произойти, когда мы не полностью осознаем порядок выполнения кода, и проблемы, связанные с попыткой трактовать асинхронный код как синхронный. Следующий пример довольно похож на тот, что мы видели раньше (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/async-sync.html">запустите пример</a>, и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync.html">посмотрте исходный код</a>). Одно из отличий состоит в том, что мы включили ряд операторов {{domxref("console.log()")}} чтобы проиллюстрировать порядок, в котором, как вы думаете, будет выполняться код.</p> + +<pre class="brush: js notranslate">console.log ('Starting'); +let image; + +fetch('coffee.jpg').then((response) => { + console.log('It worked :)') + return response.blob(); +}).then((myBlob) => { + let objectURL = URL.createObjectURL(myBlob); + image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}).catch((error) => { + console.log('There has been a problem with your fetch operation: ' + error.message); +}); + +console.log ('All done!');</pre> + +<p>Браузер начнет выполнение кода, увидит первый консольный оператор <code>(Starting)</code> и выполнит его, а затем создаст переменную <code>image</code>.</p> + +<p>Затем он переместится на следующую строку и начнет выполнять блок <code>fetch ()</code>, но, поскольку <code>fetch ()</code> выполняется асинхронно без блокировки, выполнение кода продолжается после кода, связанного с промисом, тем самым достигая окончательного оператора (<code>All done!</code>) и выводя его на консоль.</p> + +<p>Только после того, как блок <code>fetch ()</code> полностью завершит работу и доставит свой результат через блоки <code>.then ()</code>, мы наконец увидим второе сообщение <code>console.log ()</code> (<code>It worked ;)</code>). Таким образом, сообщения появились не в том порядке, который вы могли ожидать:</p> + +<ul> + <li>Starting</li> + <li>All done!</li> + <li>It worked :)</li> +</ul> + +<p>Если вы запутались, рассмотрим следующий небольшой пример:</p> + +<pre class="brush: js notranslate">console.log("registering click handler"); + +button.addEventListener('click', () => { + console.log("get click"); +}); + +console.log("all done");</pre> + +<p>Этот пример очень схож с предыдущим в своем поведении — первое и третье сообщения <code>console.log ()</code> будут показаны немедленно, но второе будет заблокировано, пока кто-то не нажмет кнопку мыши. Предыдущий пример работает аналогичным образом, за исключением того, что в этом случае второе сообщение блокируется цепочкой промисов, получая ресурс, а затем отображая его на экране, а не щелчком мыши.</p> + +<p>В менее простом примере кода такая система может вызвать проблему — вы не можете включить блок асинхронного кода, который возвращает результат, на который вы потом будете полагаться в блоке синхронного кода. Вы просто не можете гарантировать, что асинхронная функция вернется до того, как браузер обработает синхронный блок.</p> + +<p>Чтобы увидеть это в действии, попробуйте взять локальную копию нашего <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync.html">примера</a> и измените третий вызов <code>console.log ()</code> следующим образом:</p> + +<pre class="brush: js notranslate">console.log ('All done! ' + image + 'displayed.');</pre> + +<p>Теперь вместо третьего сообщения должна возникнуть следующая ошибка:</p> + +<pre class="notranslate"><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body">TypeError: image is undefined; can't access its "src" property</span></span></span></pre> + +<p>Это происходит потому, что в то же время браузер пытается запустить третий <code>console.log()</code>, блок <code>fetch()</code> еще не закончил выполнение, поэтому переменная <code>image</code> еще не имеет значения.</p> + +<div class="blockIndicator note"> +<p><strong>Заметка</strong>: Из соображений безопасности вы не можете применять <code>fetch() </code> к файлам из вашей локальной системы (или запустить другие такие операции локально); чтобы запустить локально пример выше вам необходимо запустить его через <a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server">локальный веб-сервер</a>.</p> +</div> + +<h2 id="Активное_обучение_сделайте_все_это_асинхронно!">Активное обучение: сделайте все это асинхронно!</h2> + +<p>Чтобы исправить проблемный пример с <code>fetch()</code> и заставить все три сообщения <code>console.log()</code> появиться в желаемом порядке, вы можете также запустить третье сообщение <code>console.log()</code> асинхронно. Этого можно добиться, переместив его внутрь другого блока <code>.then()</code> присоединенного к концу второго, или просто переместив его внутрь второго блока <code>then()</code>. Попробуйте иправить это сейчас..</p> + +<div class="blockIndicator note"> +<p><strong>Заметка</strong>: Если вы застряли, вы можете <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync-fixed.html">найти ответ здесь</a> (также можно посмотреть <a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/async-sync-fixed.html">запущенный пример</a>). Также вы можете найти много информации о промисах в нашем гайде <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Основные понятия асинхронного программирования</a> позднее в этом модуле.</p> +</div> + +<h2 id="Заключение">Заключение</h2> + +<p>В своей основной форме JavaScript является синхронным, блокирующим, однопоточным языком, в котором одновременно может выполняться только одна операция. Но веб-браузеры определяют функции и API, которые позволяют нам регистрировать функции, которые не должны выполняться синхронно, а должны вызываться асинхронно, когда происходит какое-либо событие (время, взаимодействие пользователя с мышью или получение данных по сети, например). Это означает, что вы можете позволить своему коду делать несколько вещей одновременно, не останавливая и не блокируя основной поток.</p> + +<p>Будем ли мы запускать код синхронно или асинхронно, будет зависеть от того, что мы пытаемся сделать.</p> + +<p>Есть моменты, когда мы хотим, чтобы все загружалось и происходило прямо сейчас. Например, при применении некоторых пользовательских стилей к веб-странице вы хотите, чтобы стили применялись как можно быстрее.</p> + +<p>Если мы выполняем операцию, которая требует времени, например, запрос к базе данных и использование полученных результатов для заполнения шаблонов, лучше вытолкнуть это из основного потока и выполнить задачу асинхронно. Со временем вы узнаете, когда имеет смысл выбирать асинхронную технику вместо синхронной.</p> + +<ul> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/ru/docs/Learn/JavaScript/Asynchronous/Concepts">Основные понятия асинхронного программирования</a></li> + <li><a href="https://wiki.developer.mozilla.org/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/ru/learn/javascript/asynchronous/таймауты_и_интервалы/index.html b/files/ru/learn/javascript/asynchronous/таймауты_и_интервалы/index.html new file mode 100644 index 0000000000..00506c2a14 --- /dev/null +++ b/files/ru/learn/javascript/asynchronous/таймауты_и_интервалы/index.html @@ -0,0 +1,638 @@ +--- +title: 'Объединенный асинхронный JavaScript: Таймайты и интервалы' +slug: Learn/JavaScript/Asynchronous/Таймауты_и_интервалы +translation_of: 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">В этом руководстве рассматриваются традиционные методы, доступные в JavaScript для асинхронного выполнения кода по истечении заданного периода времени или через регулярный интервал (например, заданное количество раз в секунду), обсуждаются их полезные свойства и рассматриваются присущие им проблемы. .</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="Введение">Введение</h2> + +<p>В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяли им асинхронно выполнять код по истечении определенного временного интервала и повторно выполнять асинхронный блок кода, пока вы не скажете ему остановиться.</p> + +<p>Эти функции:</p> + +<dl> + <dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code></dt> + <dd>Выполняет указанный блок кода один раз по истечении указанного времени</dd> + <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> + +<div> +<p>Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval (). В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут еще больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после того, как станет доступен основной поток. (Другими словами, когда стек пуст.) Вы узнаете больше по этому вопросу по мере изучения этой статьи.</p> +</div> + +<p>В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении. В следующих разделах мы покажем вам, как их можно использовать.</p> + +<h2 id="setTimeout">setTimeout()</h2> + +<p>Как мы ранее отметили, setTimeout () выполняет определенный блок кода один раз по истечении заданного времени. Принимает следующие параметры:</p> + +<ul> + <li>Функция для запуска или ссылка на функцию, определенную в другом месте.</li> + <li>Число, представляющее интервал времени в миллисекундах (1000 миллисекунд равняется 1 секунде) ожидания перед выполнением кода. Если вы укажете значение 0 (или просто опустите значение), функция запустится как можно скорее. (См. Примечание ниже о том, почему он запускается «как можно скорее», а не «сразу».) Подробнее о том, почему вы, возможно, захотите сделать это позже.</li> + <li>Значений, представляющие любые параметры, которые вы хотите передать функции при ее запуске.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>NOTE: </strong> Указанное время (или задержка) не является гарантированным временем выполнения, а скорее минимальным временем выполнения. Обратные вызовы, которые вы передаете этим функциям, не могут выполняться, пока стек в основном потоке не станет пустым.</p> + +<p>Как следствие, такой код, как setTimeout (fn, 0), будет выполняться, как только стек будет пуст, а не сразу. Если вы выполните такой код, как setTimeout (fn, 0), но сразу после выполнения цикла, который насчитывает от 1 до 10 миллиардов, ваш обратный вызов будет выполнен через несколько секунд.</p> +</div> + +<p>В слудующем примере, браузер будет ожидать две секунды перед тем как выполнит анонимную функцию, тогда отобразит сообщение (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">живой пример</a>, и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">исходный код</a>):</p> + +<pre class="brush: js notranslate">let myGreeting = setTimeout(function() { + alert('Hello, Mr. Universe!'); +}, 2000)</pre> + +<p>Указанные вами функции не обязательно должны быть анонимными. Вы можете дать своей функции имя и даже определить ее где-нибудь еще и передать ссылку на функцию в setTimeout (). Следующие две версии фрагмента кода эквивалентны первой:</p> + +<pre class="brush: js notranslate">// С именованной функцией +let myGreeting = setTimeout(function sayHi() { + alert('Hello, Mr. Universe!'); +}, 2000) + +// С функцией определенной отдельно +function sayHi() { + alert('Hello Mr. Universe!'); +} + +let myGreeting = setTimeout(sayHi, 2000);</pre> + +<p>Это может быть полезно, если у вас есть функция, которую нужно вызывать как по таймауту, так например и в ответ на событие. Но это также может помочь поддерживать ваш код в чистоте, особенно если обратный вызов тайм-аута занимает больше, чем несколько строк кода.</p> + +<p><code>setTimeout () </code>возвращает значение идентификатора, которое можно использовать для ссылки на тайм-аут позже, например, когда вы хотите его остановить.</p> + +<h3 id="Передача_параметров_в_функцию_setTimeout">Передача параметров в функцию setTimeout ()</h3> + +<p>Любые параметры, которые вы хотите передать функции, выполняемой внутри setTimeout (), должны быть переданы ей как дополнительные параметры в конце списка.</p> + +<p>Например, вы можете реорганизовать предыдущую функцию, чтобы она передавала привет любому имени, переданному ей:</p> + +<pre class="brush: js notranslate">function sayHi(who) { + alert(`Hello ${who}!`); +}</pre> + +<p>Теперь вы можете передать имя в вызов setTimeout () в качестве третьего параметра:</p> + +<pre class="brush: js notranslate">let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');</pre> + +<h3 id="Очистка_таймаутов">Очистка таймаутов</h3> + +<p>Наконец, если был создан тайм-аут, вы можете отменить его до истечения указанного времени, вызвав <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout">clearTimeout()</a></code>, передав ему идентификатор вызова <code>setTimeout()</code> в качестве параметра. Итак, чтобы отменить указанный выше тайм-аут, вы должны сделать следующее:</p> + +<pre class="brush: js notranslate">clearTimeout(myGreeting);</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: См.<code><a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/greeter-app.html">greeter-app.html</a></code> для более полной демонстрации, которая позволяет вам указать имя для приветствия и отменить приветствие с помощью отдельной кнопки (<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/greeter-app.html">см. исходный код</a>).</p> +</div> + +<h2 id="setInterval">setInterval()</h2> + +<p><code>setTimeout ()</code> отлично работает, когда вам нужно один раз запустить код по истечении заданного периода времени. Но что происходит, когда вам нужно запускать код снова и снова - например, в случае анимации?</p> + +<p>Здесь пригодится <a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a> . Работает очень похоже на setTimeout (), за исключением того, что функция, которую вы передаете в качестве первого параметра, выполняется повторно не менее чем за количество миллисекунд, заданных вторым параметром. Вы также можете передать любые параметры, необходимые для выполняемой функции, в качестве последующих параметров вызова setInterval ().</p> + +<p>Давайте посмотрим на пример. Следующая функция создает новый объект <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">Date()</a></code>, с помощью <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString">toLocaleTimeString()</a></code> извлекает из него строку с временем и отображает ее в пользовательском интерфейсе. Затем он запускает функцию один раз в секунду с помощью <code>setInterval()</code>, создавая эффект цифровых часов, которые обновляются раз в секунду (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-clock.html"> реальный пример</a>, и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">исходный код</a>):</p> + +<pre class="brush: js notranslate">function displayTime() { + let date = new Date(); + let time = date.toLocaleTimeString(); + document.getElementById('demo').textContent = time; +} + +const createClock = setInterval(displayTime, 1000);</pre> + +<p>Как и <code>setTimeout ()</code>, <code>setInterval ()</code> возвращает определенное значение, которое вы можете использовать позже, когда вам нужно очистить интервал.</p> + +<h3 id="Очистка_интервала">Очистка интервала</h3> + +<p><code>setInterval () </code>выполняет задачу постоянно. setInterval () продолжает выполнять задачу вечно, если вы что-то с ней не сделаете. Возможно, вам понадобится способ остановить такие задачи, иначе вы можете получить ошибки, если браузер не сможет выполнить какие-либо другие версии задачи или если анимация, обрабатываемая задачей, завершилась. Вы можете сделать это так же, как останавливаете <code>timeouts</code> - передавая идентификатор, возвращаемый вызовом <code>setInterval ()</code>, в функцию <code>clearInterval ()</code>:</p> + +<pre class="brush: js notranslate">const myInterval = setInterval(myFunction, 2000); + +clearInterval(myInterval);</pre> + +<h4 id="Активное_обучение_Создание_собственного_секундомера!">Активное обучение: Создание собственного секундомера!</h4> + +<p>Учитывая все вышесказанное, у нас есть для вас задача. Возьмите копию нашего примера <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">setInterval-clock.html</a></code> , и измените ее так, чтобы создать свой собственный простой секундомер.</p> + +<p>Вам нужно отображать время, как и раньше, но в этом примере вам нужно:</p> + +<ul> + <li>Кнопка "Start" для запуска секундомера.</li> + <li>Кнопка "Stop" для паузы/остановки.</li> + <li>Кнопка "Reset", чтобы сбросить счетчик времени на <code>0</code>.</li> + <li>Дисплей времени, чтобы отображать количество прошедших секунд а не фактическое время.</li> +</ul> + +<p>Несколько подсказок для вас:</p> + +<ul> + <li>Вы можете структурировать и стилизовать разметку кнопок по своему усмотрению; просто убедитесь, что вы используете семантический HTML с ковычками, которые позволяют захватывать ссылки на кнопки с помощью JavaScript.</li> + <li>Вероятно, вы захотите создать переменную, которая начинается с 0, а затем увеличивается на единицу каждую секунду с использованием постоянного цикла.</li> + <li>Этот пример проще создать без использования объекта Date (), как мы это делали в нашей версии, но он будет менее точен - вы не можете гарантировать, что обратный вызов сработает ровно через 1000 мс. Более точным способом было бы запустить startTime = Date.now (), чтобы получить метку времени, когда пользователь нажал кнопку запуска, а затем выполнить Date.now () - startTime, чтобы получить количество миллисекунд после того, как была нажата кнопка запуска .</li> + <li>Вам также нужно рассчитать количество часов, минут и секунд как отдельные значения, а затем отображать их вместе в строке после каждой итерации цикла. На втором счетчике вы можете отработать каждую из них.</li> + <li>Как вы могли бы их рассчитать? Подумайте об этом: + <ul> + <li>В одном часе <code>3600 </code>секунд.</li> + <li>Количество минут - это количество секунд, оставшееся после вычитания всех часов, разделенное на 60.</li> + <li>Количество секунд будет количеством секунд, оставшихся после вычитания всех минут.</li> + </ul> + </li> + <li>Вам необходимо включить начальный ноль в отображаемые значения, если сумма меньше <code>10</code>, чтобы они больше походили на традиционные часы.</li> + <li>Чтобы приостановить секундомер, вам нужно очистить интервал. Чтобы сбросить его, вам нудно установить счетчик обратно на <code>0</code>, очистить интервал, а затем немедленно обновить отображение.</li> + <li>Вероятно, вам следует отключить кнопку запуска после ее нажатия один раз и снова включить ее после того, как вы остановили / сбросили ее. В противном случае многократное нажатие кнопки запуска приведет к применению нескольких <code>setInterval ()</code> к часам, что приведет к неправильному поведению.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Если вы застрали, вы можете <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">увидеть нашу версию</a> (см. также <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">исходный код</a> ).</p> +</div> + +<h2 id="Что_нужно_помнить_о_setTimeout_и_setInterval">Что нужно помнить о <code>setTimeout ()</code> и <code>setInterval ()</code></h2> + +<p>При работе с<code> setTimeout ()</code> и <code>setInterval ()</code> следует помнить о нескольких вещах. Давайте рассмотрим их.</p> + +<h3 id="Рекурсивые_таймауты">Рекурсивые таймауты</h3> + +<p>Есть еще один способ использования <code>setTimeout ()</code>: вы можете вызвать его рекурсивно для повторного запуска одного и того же кода вместо использования <code>setInterval ()</code>.</p> + +<p>В приведенном ниже примере используется рекурсивный setTimeout () для запуска переданной функции каждые 100 миллисекунд:</p> + +<pre class="brush: js notranslate">let i = 1; + +setTimeout(function run() { + console.log(i); + i++; + setTimeout(run, 100); +}, 100);</pre> + +<p>Сравните приведенный выше пример со следующим - здесь используется <code>setInterval ()</code> для достижения того же эффекта:</p> + +<pre class="brush: js notranslate">let i = 1; + +setInterval(function run() { + console.log(i); + i++ +}, 100);</pre> + +<h4 id="Чем_рекурсивный_setTimeout_отличается_от_setInterval">Чем рекурсивный <code>setTimeout ()</code> отличается от <code>setInterval () </code>?</h4> + +<p>Разница между двумя версиями приведенного выше кода невелика.</p> + +<ul> + <li>Рекурсивный <code>setTimeout ()</code> гарантирует такую же задержку между выполнениями. (Например, 100 мс в приведенном выше случае.) Код будет запущен, затем подождет 100 миллисекунд, прежде чем запустится снова, поэтому интервал будет одинаковым, независимо от того, сколько времени требуется для выполнения кода.</li> + <li>Пример с использованием <code>setInterval () </code>работает несколько иначе. Выбранный вами интервал включает время, затрачиваемое на выполнение кода, который вы хотите запустить. Предположим, что выполнение кода занимает <code>40 </code>миллисекунд - тогда интервал составляет всего <code>60 </code>миллисекунд.</li> + <li>При рекурсивном использовании <code>setTimeout ()</code> каждая итерация может вычислять различную задержку перед запуском следующей итерации. Другими словами, значение второго параметра может указывать другое время в миллисекундах для ожидания перед повторным запуском кода.</li> +</ul> + +<p>Когда ваш код потенциально может занять больше времени, чем назначенный вами интервал времени, лучше использовать рекурсивный <code>setTimeout ()</code> - это сохранит постоянный временной интервал между выполнениями независимо от того, сколько времени потребуется для выполнения кода, и вы избежите ошибок.</p> + +<h3 id="Немедленные_таймауты">Немедленные таймауты</h3> + +<p>Использование 0 в качестве значения для <code>setTimeout ()</code> позволяет планировать выполнение указанной функции обратного вызова как можно скорее, но только после того, как будет запущен основной поток кода.</p> + +<p>Например, код приведенный ниже (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/zero-settimeout.html">рабочий код</a>) выводит alert содержащий <code>"Hello"</code>, затем alert содержащий <code>"World"</code> как только вы нажмете ОК в первом alert.</p> + +<pre class="brush: js notranslate">setTimeout(function() { + alert('World'); +}, 0); + +alert('Hello');</pre> + +<p>Это может быть полезно в тех случаях, когда вы хотите установить блок кода для запуска, как только весь основной поток завершит работу - поместите его в цикл событий async, чтобы он запускался сразу после этого.</p> + +<h3 id="Очистка_с_помощью_clearTimeout_или_clearInterval">Очистка с помощью <code>clearTimeout()</code> или <code>clearInterval()</code></h3> + +<p>clearTimeout () и <code>clearInterval ()</code> используют один и тот же список записей для очистки. Интересно, что это означает, что вы можете использовать любой метод для очистки setTimeout () или setInterval ().</p> + +<p>Для согласованности следует использовать <code>clearTimeout ()</code> для очистки записей <code>setTimeout ()</code> и <code>clearInterval ()</code> для очистки записей <code>setInterval ()</code>. Это поможет избежать путаницы.</p> + +<h2 id="requestAnimationFrame">requestAnimationFrame()</h2> + +<p><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code> это специализированная функция цикла, созданная для эффективного запуска анимации в браузере. По сути, это современная версия <code>setInterval ()</code> - она выполняет указанный блок кода до того, как браузер перерисовывает изображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.</p> + +<p>Он был создан в ответ на проблемы с <code>setInterval ()</code>, который, например, не работает с частотой кадров, оптимизированной для устройства, иногда пропускает кадры, продолжает работать, даже если вкладка не является активной вкладкой или анимация прокручивается со страницы и т. д.(<a href="http://creativejs.com/resources/requestanimationframe/index.html">Читай об этом больше в CreativeJS</a>.)</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Вы можете найти примеры использования <code>requestAnimationFrame()</code> в этом курсе — например в <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Рисование графики</a>, and <a href="/en-US/docs/Learn/JavaScript/Objects/Object_building_practice">Практика построения объектов</a>.</p> +</div> + +<p>Метод принимает в качестве аргумента обратный вызов, который должен быть вызван перед перерисовкой. Это общий шаблон, в котором он используется:</p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>Идея состоит в том, чтобы определить функцию, в которой ваша анимация обновляется (например, ваши спрайты перемещаются, счет обновляется, данные обновляются или что-то еще). Затем вы вызываете его, чтобы начать процесс. В конце функционального блока вы вызываете <code>requestAnimationFrame ()</code> со ссылкой на функцию, переданной в качестве параметра, и это дает браузеру указание вызвать функцию снова при следующей перерисовке дисплея. Затем он выполняется непрерывно, поскольку код рекурсивно вызывает <code>requestAnimationFrame ().</code></p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Если вы хотите выполнить простое постоянное анимирование DOM , <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS Анимация</a> вероятно будет быстрее. Она высисляется непосредственно внутренним кодом браузера, а не JavaScript.</p> + +<p>Однако, если вы делаете что-то более сложное, включающее объекты, которые не доступны напрямую в the DOM (такие как <a href="/en-US/docs/Web/API/Canvas_API">2D Canvas API</a> или <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> ), <code>requestAnimationFrame()</code> предпочтительный вариант в большинстве случаев.</p> +</div> + +<h3 id="Как_быстро_работает_ваша_анимация">Как быстро работает ваша анимация?</h3> + +<p>Плавность анимации напрямую зависит от частоты кадров анимации и измеряется в кадрах в секунду (fps). Чем выше это число, тем плавнее будет выглядеть ваша анимация до точки.</p> + +<p>Поскольку большинство экранов имеют частоту обновления 60 Гц, максимальная частота кадров, к которой вы можете стремиться, составляет 60 кадров в секунду (FPS) при работе с веб-браузерами. Однако большее количество кадров означает больше обработки, которая часто может вызывать заикание и пропуски, также известные как пропадание кадров или заедание.</p> + +<p>Если у вас есть монитор с частотой обновления 60 Гц и вы хотите достичь 60 кадров в секунду, у вас есть около 16,7 миллисекунд <code>(1000/60)</code> для выполнения кода анимации для рендеринга каждого кадра. Это напоминание о том, что вам нужно помнить об объеме кода, который вы пытаетесь запустить во время каждого прохождения цикла анимации.</p> + +<p><code>requestAnimationFrame ()</code> всегда пытается приблизиться к этому волшебному значению 60 FPS, насколько это возможно. Иногда это невозможно - если у вас действительно сложная анимация и вы запускаете ее на медленном компьютере, частота кадров будет меньше. Во всех случаях<code> requestAnimationFrame ()</code> всегда будет делать все возможное с тем, что у него есть.</p> + +<h3 id="Чем_отличается_requestAnimationFrame_от_setInterval_and_setTimeout">Чем отличается requestAnimationFrame() от setInterval() and setTimeout()?</h3> + +<p>Давайте поговорим еще немного о том, чем метод <code>requestAnimationFrame ()</code> отличается от других методов, используемых ранее. Глядя на наш код сверху:</p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>Такой же код с использованием <code>setInterval()</code>:</p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here +} + +setInterval(draw, 17);</pre> + +<p>Как мы уже говорили ранее, вы не указываете временной интервал для <code>requestAnimationFrame ()</code>. Просто он работает максимально быстро и плавно в текущих условиях. Браузер также не тратит время на запуск, если по какой-то причине анимация выходит за пределы экрана и т. д.</p> + +<p><code>setInterval ()</code>, с другой стороны, требует указания интервала. Мы пришли к нашему окончательному значению 17 по формуле 1000 миллисекунд / 60 Гц, а затем округлили его в большую сторону. Округление - хорошая идея; если вы округлите в меньшую сторону, браузер может попытаться запустить анимацию со скоростью, превышающей 60 кадров в секунду, и в любом случае это не повлияет на плавность анимации. Как мы уже говорили, стандартная частота обновления - 60 Гц.</p> + +<h3 id="В_том_числе_временная_метка">В том числе временная метка</h3> + +<p>Фактическому обратному вызову, переданному в функцию <code>requestAnimationFrame ()</code>, также может быть задан параметр: значение отметки времени, которое представляет время с момента начала работы <code>requestAnimationFrame ().</code></p> + +<p>Это полезно, поскольку позволяет запускать вещи в определенное время и в постоянном темпе, независимо от того, насколько быстрым или медленным может быть ваше устройство. Общий шаблон, который вы бы использовали, выглядит примерно так:</p> + +<pre class="brush: js notranslate">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><code>requestAnimationFrame ()</code> поддерживается в более поздних версиях браузеров, чем s<code>etInterval ()</code> / <code>setTimeout ()</code>. Интересно, что он доступен в Internet Explorer 10 и выше.</p> + +<p>Итак, если вам не тербуется поддержка старых версий IE, нет особых причин не использовать <code>requestAnimationFrame()</code>.</p> + +<h3 id="Простой_пример">Простой пример</h3> + +<p>Хватит теории! Давайте выполним упражнение с использованием <code>requestAnimationFrame()</code> . Создадим простую анимацию "spinner animation"—вы могли ее видеть в приложениях когда происходят задержки при ответе с сервера и т.п..</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Для такой простой анимации, вам следовало бы использовать CSS . Однако такой вид анимации очень полезен для демонстрации <code>requestAnimationFrame()</code> , вы скорее всего будете использовать этот метод когда делаете что-то более сложное, например обновление отображения игры в каждом кадре.</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>Поместите пустой {{htmlelement("div")}} елемент внутри элемента {{htmlelement("body")}}, затем добавьте внутрь символ ↻ . Этот символ будет действовать как spinner в нашем примере.</p> + </li> + <li> + <p>Применитеpply следующий CSS к HTML шаблону (любым предпочитаемым способом). Он установ красный фон на странице, высоту <code><body></code> равную <code>100%</code> высоты {{htmlelement("html")}} , и центрирует <code><div></code> внутри <code><body></code>, по горизонтали и вертикали.</p> + + <pre class="brush: css notranslate">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>Разместите {{htmlelement("script")}} элемент перед <code></body></code> .</p> + </li> + <li> + <p>Разместите следующий JavaScript код в <code><script></code> . Здесь вы сохраняете ссылку на <code><div></code> внутри, устанавливаете дяпеременной <code>rotateCount</code> значение <code>0</code>, устанавливаете неинициализированную переменную, которая позже будет использоваться для хранения ссылки на вызов <code>requestAnimationFrame()</code>, и устанавливатете для переменной <code>startTime</code> значение <code>null</code>, которая будет позже использоваться для хранения времени начала <code>requestAnimationFrame()</code>.</p> + + <pre class="brush: js notranslate">const spinner = document.querySelector('div'); +let rotateCount = 0; +let startTime = null; +let rAF; +</pre> + </li> + <li> + <p>Под предыдущим котом вставьте функцию <code>draw()</code> соторая будет использоваться для хранения нашешо кода анимации, который включает параметр <code>timestamp</code> :</p> + + <pre class="brush: js notranslate">function draw(timestamp) { + +}</pre> + </li> + <li> + <p>Внутри <code>draw ()</code> добавьте следующие строки. Они определят время начала, если оно еще не определено (это произойдет только на первой итерации цикла), и установят для параметра <code>rotateCount</code> значение для поворота счетчика (текущая временная метка, возьмите начальную временную метку, разделенную на три, чтобы замедлиться):</p> + + <pre class="brush: js notranslate"> if (!startTime) { + startTime = timestamp; + } + + rotateCount = (timestamp - startTime) / 3; +</pre> + </li> + <li> + <p>Под предыдущей строкой внутри <code>draw ()</code> добавьте следующий блок - он проверяет, превышает ли значение <code>rotateCount 359</code> (например, <code>360</code>, полный круг). Если это так, он устанавливает значение по модулю <code>360</code> (то есть остаток, оставшийся после деления значения на <code>360</code>), поэтому круговая анимация может продолжаться непрерывно с разумным низким значением. Обратите внимание, что это не является строго необходимым, но легче работать со значениями от 0 до <code>359</code> градусов, чем со значениями типа «<code>128000</code> градусов».</p> + + <pre class="brush: js notranslate">if (rotateCount > 359) { + rotateCount %= 360; +}</pre> + </li> + <li>Затем, под предыдущим блоком, добавьте следующую строку, чтобы вращать spinner: + <pre class="brush: js notranslate">spinner.style.transform = `rotate(${rotateCount}deg)`;</pre> + </li> + <li> + <p>В самом низу внутри функции <em>draw ()</em> вставьте следующую строку. Это ключ ко всей операции - вы устанавливаете для переменной, определенной ранее, активный вызов<em> requestAnimation ()</em>, который принимает функцию <em>draw ()</em> в качестве своего параметра. Это запускает анимацию, постоянно выполняя функцию <em>draw ()</em> со скоростью, близкой к 60 FPS.</p> + + <pre class="brush: js notranslate">rAF = requestAnimationFrame(draw);</pre> + </li> + <li> + <p>Ниже, вызовите функцию <code>draw()</code> для запуска анимации.</p> + + <pre class="brush: js notranslate">draw();</pre> + </li> +</ol> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Вы можете посмотреть <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">рабочий образец на GitHub</a>. ( <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">исходный код</a>.)</p> +</div> + +<h3 id="Очbстка_вызова_requestAnimationFrame">Очbстка вызова requestAnimationFrame() </h3> + +<p>Очистить вызов <code>requestAnimationFrame ()</code> можно, вызвав соответствующий метод <code>cancelAnimationFrame ()</code>. (Обратите внимание, что имя функции начинается с «cancel», а не «clear», как у методов «set ...».)</p> + +<p>Просто передайте ему значение, возвращаемое вызовом requestAnimationFrame () для отмены, которое вы сохранили в переменной rAF:</p> + +<pre class="brush: js notranslate">cancelAnimationFrame(rAF);</pre> + +<h3 id="Активное_обучение_запуск_и_остановка_нашей_анимации">Активное обучение: запуск и остановка нашей анимации</h3> + +<p>В этом упражнении мы хотели бы, чтобы вы протестировали метод <code>cancelAnimationFrame ()</code>, взяв наш предыдущий пример и обновив его, добавив прослушиватель событий для запуска и остановки счетчика при щелчке мышью в любом месте страницы.</p> + +<p>Подсказки:</p> + +<ul> + <li>Обработчик события щелчка можно добавить к большинству элементов, включая документ <code><body></code>. Имеет смысл поместить его в элемент <code><body></code>, если вы хотите максимизировать интерактивную область - событие всплывает до его дочерних элементов.</li> + <li>Вы захотите добавить переменную отслеживания, чтобы проверить, вращается ли счетчик или нет, очистив кадр анимации, если он есть, и снова вызвать его, если это не так.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Для начала попробуйте сами; если вы действительно застряли, посмотрите наш <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">живой пример</a> и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">исходный код</a>.</p> +</div> + +<h3 id="Регулировка_анимации_requestAnimationFrame">Регулировка анимации <code>requestAnimationFrame()</code> </h3> + +<p>Одним из ограничений <code>requestAnimationFrame ()</code> является то, что вы не можете выбирать частоту кадров. В большинстве случаев это не проблема, так как обычно вы хотите, чтобы ваша анимация работала как можно плавнее. Но как насчет того, чтобы создать олдскульную 8-битную анимацию?</p> + +<p>Это было проблемой, например в анимации ходьбы, вдохновленной островом обезьян, из статьи <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing Graphics</a>:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p> + +<p>В этом примере вы должны анимировать как положение персонажа на экране, так и отображаемый спрайт. В анимации спрайта всего 6 кадров. Если бы вы показывали разные кадры спрайта для каждого кадра, отображаемого на экране, с помощью requestAnimationFrame (), Guybrush двигал бы конечностями слишком быстро, и анимация выглядела бы нелепо. Следовательно, в этом примере регулируется скорость, с которой спрайт циклически повторяет свои кадры, используя следующий код:</p> + +<pre class="brush: js notranslate">if (posX % 13 === 0) { + if (sprite === 5) { + sprite = 0; + } else { + sprite++; + } +}</pre> + +<p>Таким образом, код циклически повторяет спрайт только один раз каждые 13 кадров анимации.</p> + +<p>... Фактически, это примерно каждые 6,5 кадров, поскольку мы обновляем posX (положение персонажа на экране) на два кадра:</p> + +<pre class="brush: js notranslate">if (posX > width/2) { + newStartPos = -( (width/2) + 102 ); + posX = Math.ceil(newStartPos / 13) * 13; + console.log(posX); +} else { + posX += 2; +}</pre> + +<p>Это код, который вычисляет, как обновлять позицию в каждом кадре анимации.</p> + +<p>Метод, который вы используете для регулирования анимации, будет зависеть от вашего конкретного кода. Например, в предыдущем примере счетчика вы могли заставить его двигаться медленнее, увеличивая rotateCount только на единицу в каждом кадре вместо двух.</p> + +<h2 id="Активное_обучение_игра_на_реакцию">Активное обучение: игра на реакцию</h2> + +<p>В последнем разделе этой статьи вы создадите игру на реакцию для двух игроков. В игре будет два игрока, один из которых управляет игрой с помощью клавиши <kbd>A</kbd>, а другой - с помощью клавиши<kbd> L</kbd>.</p> + +<p>При нажатии кнопки «Start» счетчик, подобный тому, что мы видели ранее, отображается в течение случайного промежутка времени от 5 до 10 секунд. По истечении этого времени появится сообщение «PLAYERS GO !!» - как только это произойдет, первый игрок, который нажмет свою кнопку управления, выиграет игру.</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}</p> + +<p>Давайте поработаем над этим:</p> + +<ol> + <li> + <p>Прежде всего, скачайте <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game-starter.html">стартовый файл</a>. Он содержит законченную структуру HTML и стили CSS, что дает нам игровую доску, которая показывает информацию двух игроков (как показано выше), но с счетчиком и параграфом результатов, отображаемыми друг над другом. Вам нужно просто написать JavaScript код.</p> + </li> + <li> + <p>Внутри пустого элемента {{htmlelement("script")}} на вашей старнице, начните с добавления следующих строк кода, котороые определяют некотороые переменные и константы, которые вам понадобятся в дальнейшем:</p> + + <pre class="brush: js notranslate">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>В следующем порядке:</p> + + <ol> + <li>Ссылка на спиннер, чтобы вы могли его анимировать.</li> + <li>Ссылка на элемент {{htmlelement("div")}} содержащий спиннер, используемый для отображения и скрытия.</li> + <li>Счетчик поворотов. Он определяет, на сколько вы хотите показывать вращение спиннера на каждом кадре анимации.</li> + <li>Нулевое время начала. Это будет заполнено временем начала, когда счетчик начнет вращаться.</li> + <li>Неинициализировання переменная для последующего хранения вызова {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} который анимирует спиннер.</li> + <li>Ссылка на кнопку Start .</li> + <li>Ссылка на параграф результатов.</li> + </ol> + </li> + <li> + <p>Ниже добавьте следующую функцию. Она просто берет два числа и возвращает случайное число между ними. Это понадобится вам позже, чтобы сгенерировать случайный интервал ожидания.</p> + + <pre class="brush: js notranslate">function random(min,max) { + var num = Math.floor(Math.random()*(max-min)) + min; + return num; +}</pre> + </li> + <li> + <p>Затем добавьте функцию draw(), которая анимирует спиннер. Это очень похоже на версию из предыдущего примера просторо счетчика:</p> + + <pre class="brush: js notranslate">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>Теперь пришло время настроить начальное состояние приложения при первой загрузке страницы. Добавьте следующие две строки, которые просто скрывают абзац результатов и контейнер счетчика с помощью <code>display: none</code> ;.</p> + + <pre class="brush: js notranslate">result.style.display = 'none'; +spinnerContainer.style.display = 'none';</pre> + </li> + <li> + <p>Затем определите функцию<code> reset ()</code>, которая возвращает приложение в исходное состояние, необходимое для повторного запуска игры после ее завершения. Добавьте в конец кода следующее:</p> + + <pre class="brush: js notranslate">function reset() { + btn.style.display = 'block'; + result.textContent = ''; + result.style.display = 'none'; +}</pre> + </li> + <li> + <p>Хорошо, хватит подготовки! Пришло время сделать игру доступной! Добавьте в свой код следующий блок. Функция <code>start ()</code> вызывает <code>draw ()</code>, чтобы запустить вращение спиннера и отобразить его в пользовательском интерфейсе, скрыть кнопку Start, чтобы вы не могли испортить игру, запустив ее несколько раз одновременно, и запускает вызов <code>setTimeout ()</code>, который выполняется функция <code>setEndgame ()</code> по прошествии случайного интервала от 5 до 10 секунд. Следующий блок также добавляет прослушиватель событий к вашей кнопке для запуска функции <code>start ()</code> при ее нажатии.</p> + + <pre class="brush: js notranslate">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>: Вы увидете, что этот пример вызывает <code>setTimeout()</code> без сохранения возвращаемого значения. (не <code>let myTimeout = setTimeout(functionName, interval)</code>.) </p> + + <p>Это прекрасно работает, если вам не нужно очищать интервал / тайм-аут в любой момент. Если вы это сделаете, вам нужно будет сохранить возвращенный идентификатор!</p> + </div> + + <p>Конечным результатом предыдущего кода является то, что при нажатии кнопки «Start» отображается спиннер, и игроки вынуждены ждать произвольное количество времени, прежде чем их попросят нажать их кнопку. Эта последняя часть обрабатывается функцией <code>setEndgame ()</code>, которую вы определите позже.</p> + </li> + <li> + <p>Добавьте в свой код следующую функцию:</p> + + <pre class="brush: js notranslate">function setEndgame() { + cancelAnimationFrame(rAF); + spinnerContainer.style.display = 'none'; + result.style.display = 'block'; + result.textContent = 'PLAYERS GO!!'; + + document.addEventListener('keydown', keyHandler); + + function keyHandler(e) { + let isOver = false; + console.log(e.key); + + if (e.key === "a") { + result.textContent = 'Player 1 won!!'; + isOver = true; + } else if (e.key === "l") { + result.textContent = 'Player 2 won!!'; + isOver = true; + } + + if (isOver) { + document.removeEventListener('keydown', keyHandler); + setTimeout(reset, 5000); + } + }; +}</pre> + + <p>Пройдите через это:</p> + + <ol> + <li>Во-первых, отмените анимацию спиннера с помощью {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}} (всегда полезно очистить ненужный процессы), и скройте контейнер счетчика.</li> + <li>Затем, отобразите абзац с результатами и установите для его текстового содержимого значение "PLAYERS GO!!" чтобы сообщить игрокам, что теперь они могут нажать свою кнопку, чтобы победить.</li> + <li>Прикрепите к документу прослушиватель событий <code><a href="/en-US/docs/Web/API/Document/keydown_event">keydown</a></code> . При нажатии любой кнопки запускается функция <code>keyHandler()</code>.</li> + <li>Внутри <code>keyHandler()</code>, код включает обьект события в качестве параметра (представленного <code>e</code>) — его свойство {{domxref("KeyboardEvent.key", "key")}} содержит только что нажатую клавишу, и вы можете использовать это для твета на определенные нажатия клавиш определенными действиями.</li> + <li>Установите для переменной <code>isOver</code> значение false, чтобы мы могли отслеживать, были ли нажаты правильные клавиши, чтобы игрок 1 или 2 выиграл. Мы не хотим, чтобы игра заканчивалась при нажатии неправильной клваиши.</li> + <li>Регистрация <code>e.key</code> в консоли, это полезный способ узнать значение различных клавиш, которые вы нажимаете.</li> + <li>Когда <code>e.key</code> принимает значение "a", отобразить сообщение о том, что Player 1 выиграл, а когда <code>e.key</code> это "l", отобразить сообщение о том, что Player 2 выиграл. (<strong>Note:</strong> Это будет работать только со строчными буквами a и l — если переданы прописные A или L , это считается другими клавишами!) Если была нажата одна из этих клавишь, установите для <code>isOver</code> значение <code>true</code>.</li> + <li>Только еслиf <code>isOver</code> равно <code>true</code>, удалите прослушиватель событий <code>keydown</code> с помощью {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} чтобы после того, как произошло выигрышное нажатие, больше не было возможности ввода с клавиатуры, чтобы испортить финальный результат игры. Вы также используете <code>setTimeout()</code> для вызова <code>reset()</code> через 5 секунд — как обьяснялось ранее, эта функция сбрасывает игру обратно в исходное состояние, чтобы можно было начать новую игру.</li> + </ol> + </li> +</ol> + +<p>Вот и все - вы справились!</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Если вы где то застряли, взгляните на <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html">наша версия игры</a> (см. также <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game.html">исходный код</a> ).</p> +</div> + +<h2 id="Заключение">Заключение</h2> + +<p>Вот и все — все основы асинхронных циклов и интервалов рассмотрены в статье. Вы найдете эти методы полезными во многих ситуациях, но постарайтесь не злоупотреблять ими! Поскольку они по-прежнему выполняются в основном потоке, тяжелые и интенсивные обратные вызовы (особенно те, которые управляют DOM) могут действительно замедлить страницу, если вы не будете осторожныl.</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">Основные понятия асинхронного программирования</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Введение в асинхронный JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Обьединенный асинхронный JavaScript: Таймауты и интервалы</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">Сделайте асинхронное программирование легче с async и await</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li> +</ul> + +<div id="gtx-trans" style="position: absolute; left: 70px; top: 13746px;"> +<div class="gtx-trans-icon"></div> +</div> |