aboutsummaryrefslogtreecommitdiff
path: root/files/ru/web/javascript/guide/using_promises/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'files/ru/web/javascript/guide/using_promises/index.html')
-rw-r--r--files/ru/web/javascript/guide/using_promises/index.html321
1 files changed, 321 insertions, 0 deletions
diff --git a/files/ru/web/javascript/guide/using_promises/index.html b/files/ru/web/javascript/guide/using_promises/index.html
new file mode 100644
index 0000000000..a910dfca09
--- /dev/null
+++ b/files/ru/web/javascript/guide/using_promises/index.html
@@ -0,0 +1,321 @@
+---
+title: Использование промисов
+slug: Web/JavaScript/Guide/Ispolzovanie_promisov
+tags:
+ - JavaScript
+ - Асинхронность
+ - Гайд
+ - промис
+translation_of: Web/JavaScript/Guide/Using_promises
+---
+<div>
+<p>{{jsSidebar("Руководство по JavaScript")}}{{PreviousNext("Web/JavaScript/Guide/Details_of_the_Object_Model", "Web/JavaScript/Guide/Iterators_and_Generators")}}</p>
+</div>
+
+<p class="summary">{{jsxref("Promise")}} (промис, англ. "обещание") - это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Так как большинство людей пользуются уже созданными промисами, это руководство начнем с объяснения использования вернувшихся промисов до объяснения принципов создания. </p>
+
+<p>В сущности, промис - это возвращаемый объект, в который вы записываете два коллбэка вместо того, чтобы передать их функции.</p>
+
+<p>Например, вместо старомодной функции, которая принимает два коллбэка и вызывает один из них в зависимости от успешного или неудачного завершения операции:</p>
+
+<pre class="brush: js line-numbers language-js">function doSomethingOldStyle(successCallback, failureCallback) {
+ console.log("Готово.");
+ // Успех в половине случаев.
+ if (Math.random() &gt; .5) {
+ successCallback("Успех")
+ } else {
+ failureCallback("Ошибка")
+ }
+}
+
+function successCallback(result) {
+ console.log("Успешно завершено с результатом " + result);
+}
+
+function failureCallback(error) {
+ console.log("Завершено с ошибкой " + error);
+}
+
+doSomethingOldStyle(successCallback, failureCallback);
+</pre>
+
+<p>…современные функции возвращают промис, в который вы записываете ваши коллбэки:</p>
+
+<pre class="brush: js line-numbers language-js">function doSomething() {
+ return new Promise((resolve, reject) =&gt; {
+ console.log("Готово.");
+ // Успех в половине случаев.
+ if (Math.random() &gt; .5) {
+ resolve("Успех")
+ } else {
+ reject("Ошибка")
+ }
+ })
+}
+
+const promise = doSomething();
+promise.then(successCallback, failureCallback);</pre>
+
+<p>…или просто:</p>
+
+<pre class="brush: js line-numbers language-js">doSomething().then(successCallback, failureCallback);</pre>
+
+<p>Мы называем это <em>асинхронным вызовом функции</em>. У этого соглашения есть несколько преимуществ. Давайте рассмотрим их.</p>
+
+<h2 id="Гарантии">Гарантии</h2>
+
+<p>В отличие от старомодных переданных коллбэков промис дает некоторые гарантии:</p>
+
+<ul>
+ <li>Коллбэки никогда не будут вызваны до <a href="/ru/docs/Web/JavaScript/EventLoop#Никогда_не_блокируется">завершения обработки текущего события</a> в событийном цикле JavaScript.</li>
+ <li>Коллбеки, добавленные через .then даже <em>после </em>успешного или неудачного завершения асинхронной операции, будут также вызваны.</li>
+ <li>Несколько коллбэков может быть добавлено вызовом .then нужное количество раз, и они будут выполняться независимо в порядке добавления.</li>
+</ul>
+
+<p>Но наиболее непосредственная польза от промисов - цепочка вызовов (<em>chaining</em>).</p>
+
+<h2 id="Цепочка_вызовов">Цепочка вызовов</h2>
+
+<p>Общая нужда - выполнять две или более асинхронных операции одна за другой, причём каждая следующая начинается при успешном завершении предыдущей и использует результат её выполнения. Мы реализуем это, создавая цепочку вызовов промисов (<em>promise chain</em>).</p>
+
+<p>Вот в чём магия: функция <code>then</code> возвращает новый промис, отличающийся от первоначального:</p>
+
+<pre class="brush: js">let promise = doSomething();
+let promise2 = promise.then(successCallback, failureCallback);
+</pre>
+
+<p>или</p>
+
+<pre class="brush: js">let promise2 = doSomething().then(successCallback, failureCallback);
+</pre>
+
+<p>Второй промис представляет завершение не только <code>doSomething()</code>, но и функций <code>successCallback</code> или <code>failureCallback</code>, переданных Вами, а они тоже могут быть асинхронными функциями, возвращающими промис. В этом случае все коллбэки, добавленные к <code>promise2</code> будут поставлены в очередь за промисом, возвращаемым <code>successCallback</code> или <code>failureCallback</code>.</p>
+
+<p>По сути, каждый вызванный промис означает успешное завершение предыдущих шагов в цепочке.</p>
+
+<p>Раньше выполнение нескольких асинхронных операций друг за другом приводило к классической "Вавилонской башне" коллбэков:</p>
+
+<pre class="brush: js">doSomething(function(result) {
+ doSomethingElse(result, function(newResult) {
+ doThirdThing(newResult, function(finalResult) {
+ console.log('Итоговый результат: ' + finalResult);
+ }, failureCallback);
+ }, failureCallback);
+}, failureCallback);
+</pre>
+
+<p>В современных функциях мы записываем коллбэки в возвращаемые промисы - формируем цепочку промисов:</p>
+
+<pre class="brush: js">doSomething().then(function(result) {
+ return doSomethingElse(result);
+})
+.then(function(newResult) {
+ return doThirdThing(newResult);
+})
+.then(function(finalResult) {
+ console.log('Итоговый результат: ' + finalResult);
+})
+.catch(failureCallback);
+</pre>
+
+<p>Аргументы <code>then</code> необязательны, а <code>catch(failureCallback)</code> - это сокращение для <code>then(null, failureCallback)</code>. Вот как это выражено с помощью <a href="/ru/docs/Web/JavaScript/Reference/Functions/Arrow_functions">стрелочных функций</a>:</p>
+
+<pre class="brush: js">doSomething()
+.then(result =&gt; doSomethingElse(result))
+.then(newResult =&gt; doThirdThing(newResult))
+.then(finalResult =&gt; {
+ console.log(`Итоговый результат: ${finalResult}`);
+})
+.catch(failureCallback);
+</pre>
+
+<p><strong>Важно:</strong> Всегда возвращайте промисы в return, иначе коллбэки не будут сцеплены и ошибки могут быть не пойманы (стрелочные функции неявно возвращают результат, если скобки {} вокруг тела функции опущены).</p>
+
+<h3 id="Цепочка_вызовов_после_catch">Цепочка вызовов после catch</h3>
+
+<p>Можно продолжить цепочку вызовов <em>после </em>ошибки, т. е. после <code>catch</code>, что полезно для выполнения новых действий даже после того, как действие вернет ошибку в цепочке вызовов. Ниже приведен пример:</p>
+
+<pre class="syntaxbox"><code class="language-js"><span class="keyword token">new</span> <span class="class-name token">Promise</span><span class="punctuation token">(</span><span class="punctuation token">(</span>resolve<span class="punctuation token">,</span> reject<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="punctuation token">{</span>
+ console<span class="punctuation token">.</span><span class="function token">log</span><span class="punctuation token">(</span><span class="string token">'</span></code>Начало<code class="language-js"><span class="string token">'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+</code>
+ resolve();
+})
+.then(() =&gt; {
+ throw new Error('Где-то произошла ошибка');
+
+ console.log('Выведи это');
+})
+.catch(() =&gt; {
+ console.log('Выведи то');
+})
+.then(() =&gt; {
+ console.log('Выведи это, несмотря ни на что');
+});</pre>
+
+<p>В результате выведется данный текст:</p>
+
+<pre>Начало
+Выведи то
+Выведи это, несмотря ни на что</pre>
+
+<p>Заметьте, что текст "Выведи это" не вывелся, потому что "Где то произошла ошибка" привела к отказу</p>
+
+<h2 id="Распространение_ошибки">Распространение ошибки</h2>
+
+<p>Вы могли ранее заметить, что <code>failureCallback</code>  повторяется три раза  в <strong>"pyramid of doom", </strong>а в цепочке промисов всего лишь один раз:</p>
+
+<pre><code>doSomething()
+.then(result =&gt; doSomethingElse(result))
+.then(newResult =&gt; doThirdThing(newResult))
+.then(finalResult =&gt; console.log(`</code>Итоговый результат<code>: ${finalResult}`))
+.catch(failureCallback);</code></pre>
+
+<p>В основном, цепочка промисов останавливает выполнение кода, если где-либо произошла ошибка, и вместо этого ищет далее по цепочке обработчики ошибок. Это очень похоже на то, как работает синхронный код:</p>
+
+<pre><code>try {
+ let result = syncDoSomething();
+ let newResult = syncDoSomethingElse(result);
+ let finalResult = syncDoThirdThing(newResult);
+ console.log(`</code>Итоговый результат<code>: ${finalResult}`);
+} catch(error) {
+ failureCallback(error);
+}</code>
+</pre>
+
+<p>Эта симметрия с синхронным кодом лучше всего показывает себя в синтаксическом сахаре <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"><code>async</code>/<code>await</code></a> в ECMAScript 2017:</p>
+
+<pre><code>async function foo() {
+ try {
+ let result = await doSomething();
+ let newResult = await doSomethingElse(result);
+ let finalResult = await doThirdThing(newResult);
+ console.log(`</code>Итоговый результат<code>: ${finalResult}`);
+ } catch(error) {
+ failureCallback(error);
+ }
+}</code>
+</pre>
+
+<p>Работа данного кода основана на промисах. Для примера здесь используется функция <code>doSomething()</code>, которая встречалась ранее. Вы можете прочитать больше о синтаксисе <a href="https://developers.google.com/web/fundamentals/getting-started/primers/async-functions">здесь</a></p>
+
+<p>Примисы решают основную проблему  пирамид, обработку всех ошибок, даже вызовов исключений и программных ошибок. Это основа для функционального построения асинхронных операций.</p>
+
+<h2 id="Создание_промиса_вокруг_старого_коллбэка">Создание промиса вокруг старого коллбэка</h2>
+
+<p>{{jsxref("Promise")}} может быть создан с помощью конструктора. Это может понадобится только для старых API.</p>
+
+<p>В идеале, все асинхронные функции уже должны возвращать промис. Но увы, некоторые APIs до сих пор ожидают успешного или неудачного  коллбека переданных по старинке. Типичный пример: {{domxref("WindowTimers.setTimeout", "setTimeout()")}} функция:</p>
+
+<pre><code>setTimeout(() =&gt; saySomething("10 seconds passed"), 10000);</code></pre>
+
+<p>Смешивание старого коллбэк-стиля и промисов проблематично. В случае неудачного завершения <code>saySomething</code> или программной ошибки, нельзя обработать ошибку.</p>
+
+<p>К с частью мы можем обернуть функцию в промис. Хороший тон оборачивать проблематичные функции на самом низком возможном уровне, и больше никогда их не вызывать на прямую:</p>
+
+<pre><code>const wait = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
+
+wait(10000).then(() =&gt; saySomething("10 seconds")).catch(failureCallback);</code></pre>
+
+<p>В сущности, конструктор промиса становится исполнителем функции, который позволяет нам резолвить или режектить промис вручную. Так как <code>setTimeout</code> всегда успешен, мы опустили reject в этом случае.</p>
+
+<h2 id="Композиция">Композиция</h2>
+
+<p>{{jsxref("Promise.resolve()")}} и {{jsxref("Promise.reject()")}} короткий способ создать уже успешные или отклоненные промисы соответственно. Это иногда бывает полезно.</p>
+
+<p>{{jsxref("Promise.all()")}} и {{jsxref("Promise.race()")}} - два метода запустить асинхронные операции параллельно.</p>
+
+<p>Последовательное выполнение композиции возможно при помощи хитрости JavaScript:</p>
+
+<pre><code>[func1, func2].reduce((p, f) =&gt; p.then(f), Promise.resolve());</code></pre>
+
+<p>Фактически, мы превращаем массив асинхронных функций в цепочку промисов равносильно: <code>Promise.resolve().then(func1).then(func2);</code></p>
+
+<p>Это также можно сделать, объеденив композицию в функцию, в функциональном стиле программирования:</p>
+
+<pre><code>const applyAsync = (acc,val) =&gt; acc.then(val);
+const composeAsync = (...funcs) =&gt; x =&gt; funcs.reduce(applyAsync, Promise.resolve(x));</code></pre>
+
+<p><code>composeAsync</code> функция примет любое количество функций в качестве аргументов и вернет новую функцию которая примет в параметрах начальное значение, переданное по цепочке. Это удобно, потому что некоторые или все функции могут быть либо асинхронными либо синхронными, и они гарантированно выполнятся в правильной последовательности:</p>
+
+<pre><code>const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
+transformData(data);</code></pre>
+
+<p>В ECMAScript 2017, последовательные композиции могут быть выполненны более простым способом с помощью async/await:</p>
+
+<pre><code>for (const f of [func1, func2]) {
+ await f();
+}</code></pre>
+
+<h2 id="Порядок_выполнения">Порядок выполнения</h2>
+
+<p>Чтобы избежать сюрпризов, функции, переданные в <code>then</code> никогда не будут вызванны синхронно, даже с уже разрешенным промисом:</p>
+
+<pre><code>Promise.resolve().then(() =&gt; console.log(2));
+console.log(1); // 1, 2</code></pre>
+
+<p>Вместо немедленного выполнения, переданная функция встанет в очередь микрозадач, а значит выполнится, когда очередь будет пустой  в конце текущего вызова JavaScript цикла событий (event loop), т.е. очень скоро:</p>
+
+<pre><code>const wait = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
+
+wait().then(() =&gt; console.log(4));
+Promise.resolve().then(() =&gt; console.log(2)).then(() =&gt; console.log(3));
+console.log(1); // 1, 2, 3, 4</code></pre>
+
+<h2 id="Вложенность">Вложенность</h2>
+
+<p>Простые цепочки promise лучше оставлять без вложений, так как вложеность может быть результатом небрежной структуры. Смотрите <a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Guide/Ispolzovanie_promisov$edit#Common_mistakes">распространенные ошибки</a>.</p>
+
+<p>Вложенность - это управляющая структура, ограничивающая область действия операторов catch. В частности, вложенный catch только перехватывает сбои в своей области и ниже, а не ошибки выше в цепочке за пределами вложенной области. При правильном использовании это дает большую точность в извлечение ошибок:</p>
+
+<pre><code>doSomethingCritical()
+.then(result =&gt; doSomethingOptional()
+ .then(optionalResult =&gt; doSomethingExtraNice(optionalResult))
+ .catch(e =&gt; {})) // Игнорируется если необязательные параметр не выкинул исключение
+.then(() =&gt; moreCriticalStuff())
+.catch(e =&gt; console.log("Критическая ошибка: " + e.message));</code></pre>
+
+<p>Обратите внимание, что необязательный шаги здесь выделены отступом.</p>
+
+<p>Внутренний оператор catch нейтрализует и перехватывает ошибки только от doSomethingOptional() и doSomethingExtraNice(), после чего код возобновляется с помощью moreCriticalStuff(). Важно, что в случае сбоя doSomethingCritical() его ошибка перехватывается только последним (внешним) catch.</p>
+
+<h2 id="Частые_ошибки">Частые ошибки</h2>
+
+<p>В этом разделе собраны частые ошибки, возникающие при создании цепочек обещаний. Несколько таких ошибок можно увидеть в следующем примере:</p>
+
+<pre><code>// Плохой пример! Три ошибки!
+
+doSomething().then(function(result) {
+ doSomethingElse(result) // Забыл вернуть обещание из внутренней цепочки + неуместное влаживание
+ .then(newResult =&gt; doThirdThing(newResult));
+}).then(() =&gt; doFourthThing());
+// Забыл закончить цепочку методом catch</code></pre>
+
+<p>Первая ошибка это неправильно сцепить вещи между собой. Такое происходит когда мы создаем промис но забываем вернуть его. Как следствие, цепочка сломана, но правильнее было бы сказать что теперь у нас есть две независимые цепочки, соревнующиеся за право разрешится первой. Это означает <code>doFourthThing()</code> не будет ждать <code>doSomethingElse()</code> или <code>doThirdThing()</code> пока тот закончится, и будет исполнятся параллельно с ними, это ,вероятно, не то что хотел разработчик. Отдельные цепочки также имеют отдельную обработку ошибок, что приводит к необработанным ошибкам.</p>
+
+<p>Вторая ошибка это излишняя вложенность, включая первую ошибку. Вложенность также ограничивает область видимости внутренних обработчиков ошибок, если это не то чего хотел разработчик, это может привести к необработанным ошибкам. Примером этого является <a href="https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it">пример как не нужно создавать обещания</a>, который комбинирует вложенность с чрезмерным использованием конструктора обещаний для оборачивания кода который уже использует промисы.</p>
+
+<p>Третяя ошибка это забыть закончить цепочку ключевым словом <code>catch</code>. Незаконченные цепочки приводят к необработанным отторжениям обещаний в большинстве браузеров.</p>
+
+<p>Хорошим примером является всегда либо возвращать либо заканчивать цепочки обещаний, и как только вы получаете новое обещание, возвращайте его сразу же, чтобы не усложнять код излишней вложенностью:</p>
+
+<pre><code>doSomething()
+.then(function(result) {
+ return doSomethingElse(result);
+})
+.then(newResult =&gt; doThirdThing(newResult))
+.then(() =&gt; doFourthThing())
+.catch(error =&gt; console.log(error));</code></pre>
+
+<p>Обратите внимание что <code>() =&gt; x</code>  это сокращенная форма <code>() =&gt; { return x; }</code>.</p>
+
+<p>Теперь у нас имеется единственная определенная цепочка с правильной обработкой ошибок.</p>
+
+<p>Использование <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"><code>async</code>/<code>await</code></a> предотвращает большинство, если не все вышеуказанные ошибки—но взамен появляется другая частая ошибка—забыть ключевое слово <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"><code>await</code></a>.</p>
+
+<h2 id="Смотрите_также">Смотрите также</h2>
+
+<ul>
+ <li>{{jsxref("Promise.then()")}}</li>
+ <li><a href="http://promisesaplus.com/">Спецификация Promises/A+ (EN)</a></li>
+ <li><a href="http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html">Нолан Лоусон (Nolan Lawson): У нас проблемы с промисами - распространенные ошибки (EN)</a></li>
+</ul>