promise
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
```js
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
27
1-js/10-es-modern/3-promise/1-promise-settimeout/task.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
# Промисифицировать setTimeout
|
||||||
|
|
||||||
|
Напишите функцию `delay(ms)`, которая возвращает промис, переходящий в состояние `"resolved"` через `ms` миллисекунд.
|
||||||
|
|
||||||
|
Пример использования:
|
||||||
|
```js
|
||||||
|
delay(1000)
|
||||||
|
.then(() => alert("Hello!"))
|
||||||
|
````
|
||||||
|
|
||||||
|
Такая полезна для использования в других промис-цепочках.
|
||||||
|
|
||||||
|
Вот такой вызов:
|
||||||
|
```js
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
doSomeThing();
|
||||||
|
resolve();
|
||||||
|
}, ms)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Станет возможным переписать так:
|
||||||
|
```js
|
||||||
|
return delay(ms).then(doSomething);
|
||||||
|
```
|
BIN
1-js/10-es-modern/3-promise/anon.png
Normal file
After Width: | Height: | Size: 30 KiB |
|
@ -21,7 +21,7 @@ Promise -- это специальный объект, который содер
|
||||||
<ol>
|
<ol>
|
||||||
<li>Код, которому надо сделать что-то асинхронно, создаёт объект `promise` и возвращает его.</li>
|
<li>Код, которому надо сделать что-то асинхронно, создаёт объект `promise` и возвращает его.</li>
|
||||||
<li>Внешний код, получив `promise`, навешивает на него обработчики.</li>
|
<li>Внешний код, получив `promise`, навешивает на него обработчики.</li>
|
||||||
<li>По завершении асинхронного процесса асинхронный код переводит `promise` в состояние `fulfilled` (с результатом) или `rejected` (с ошибкой). При этом автоматически вызываются обработчики.</li>
|
<li>По завершении процесса асинхронный код переводит `promise` в состояние `fulfilled` (с результатом) или `rejected` (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Синтаксис создания `Promise`:
|
Синтаксис создания `Promise`:
|
||||||
|
@ -29,8 +29,9 @@ Promise -- это специальный объект, который содер
|
||||||
```js
|
```js
|
||||||
var promise = new Promise(function(resolve, reject) {
|
var promise = new Promise(function(resolve, reject) {
|
||||||
// Эта функция будет вызвана автоматически
|
// Эта функция будет вызвана автоматически
|
||||||
|
|
||||||
// В ней можно делать любые асинхронные операции,
|
// В ней можно делать любые асинхронные операции,
|
||||||
// А когда они завершаться — вызвать:
|
// А когда они завершаться — нужно вызвать одно из:
|
||||||
// resolve(результат) при успешном выполнении
|
// resolve(результат) при успешном выполнении
|
||||||
// reject(ошибка) при ошибке
|
// reject(ошибка) при ошибке
|
||||||
})
|
})
|
||||||
|
@ -61,10 +62,11 @@ promise.then(null, onRejected)
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
[smart header="Синхронный `throw` -- то же самое, что `reject`"]
|
[smart header="Синхронный `throw` -- то же самое, что `reject`"]
|
||||||
Если в функции промиса происходит ошибка или синхронный `throw`, то вызывается `reject`:
|
Если в функции промиса происходит синхронный `throw` (или иная ошибка), то вызывается `reject`:
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
var p = new Promise((resolve, reject) => {
|
var p = new Promise((resolve, reject) => {
|
||||||
|
// то же что reject(new Error("o_O"))
|
||||||
throw new Error("o_O");
|
throw new Error("o_O");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ p.catch(alert); // Error: o_O
|
||||||
var promise = new Promise((resolve, reject) => {
|
var promise = new Promise((resolve, reject) => {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// переводит в состояние fulfilled с результатом "result"
|
// переведёт промис в состояние fulfilled с результатом "result"
|
||||||
resolve("result");
|
resolve("result");
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
@ -135,6 +137,11 @@ promise
|
||||||
|
|
||||||
Конечно, вместо `setTimeout` мог бы быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс.
|
Конечно, вместо `setTimeout` мог бы быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс.
|
||||||
|
|
||||||
|
[smart header="Только один аргумент"]
|
||||||
|
Функции `resolve/reject` принимают ровно один аргумент -- результат/ошибку.
|
||||||
|
|
||||||
|
Именно он передаётся обработчикам в `.then`, как можно видеть в примерах выше.
|
||||||
|
[/smart]
|
||||||
|
|
||||||
## Promise после reject/resolve -- неизменны
|
## Promise после reject/resolve -- неизменны
|
||||||
|
|
||||||
|
@ -203,11 +210,11 @@ promise
|
||||||
|
|
||||||
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
|
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
|
||||||
|
|
||||||
Функция `loadUrl(url)` будет возвращать промис, который при успешной загрузки данных с `url` будет переходить в `fulfilled` с этими данными, а при ошибке -- в `rejected` с информацией об ошибке:
|
Функция `httpGet(url)` будет возвращать промис, который при успешной загрузки данных с `url` будет переходить в `fulfilled` с этими данными, а при ошибке -- в `rejected` с информацией об ошибке:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ autorun
|
//+ autorun
|
||||||
function loadUrl(url) {
|
function httpGet(url) {
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
|
|
||||||
|
@ -221,7 +228,9 @@ function loadUrl(url) {
|
||||||
*/!*
|
*/!*
|
||||||
} else {
|
} else {
|
||||||
*!*
|
*!*
|
||||||
reject(new Error(this.statusText));
|
var error = new Error(this.statusText);
|
||||||
|
error.code = this.status;
|
||||||
|
reject(error);
|
||||||
*/!*
|
*/!*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -244,7 +253,7 @@ function loadUrl(url) {
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
loadUrl("/article/promise/user.json")
|
httpGet("/article/promise/user.json")
|
||||||
.then(
|
.then(
|
||||||
response => alert(`Fulfilled: ${response}`),
|
response => alert(`Fulfilled: ${response}`),
|
||||||
error => alert(`Rejected: ${error}`)
|
error => alert(`Rejected: ${error}`)
|
||||||
|
@ -253,11 +262,9 @@ loadUrl("/article/promise/user.json")
|
||||||
|
|
||||||
|
|
||||||
[smart header="Метод `fetch`"]
|
[smart header="Метод `fetch`"]
|
||||||
Заметим, что ряд современных браузеров уже поддерживает [fetch](https://fetch.spec.whatwg.org) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он, конечно, гораздо мощнее, чем `loadUrl`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
|
Заметим, что ряд современных браузеров уже поддерживает [fetch](https://fetch.spec.whatwg.org) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он, конечно, гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
Далее мы увидим ещё примеры, а пока -- рассмотрим важные свойства `promise`.
|
|
||||||
|
|
||||||
|
|
||||||
## Цепочки промисов
|
## Цепочки промисов
|
||||||
|
|
||||||
|
@ -271,14 +278,14 @@ loadUrl("/article/promise/user.json")
|
||||||
<li>...И сделать код расширяемым, чтобы цепочку можно было легко продолжить.</li>
|
<li>...И сделать код расширяемым, чтобы цепочку можно было легко продолжить.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Вот код для этого, использующий функцию `loadUrl`, описанную выше:
|
Вот код для этого, использующий функцию `httpGet`, описанную выше:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// сделать запрос
|
// сделать запрос
|
||||||
loadUrl('/article/promise/user.json')
|
httpGet('/article/promise/user.json')
|
||||||
*!*
|
*!*
|
||||||
// 1. Получить данные о пользователе в JSON и передать дальше
|
// 1. Получить данные о пользователе в JSON и передать дальше
|
||||||
*/!*
|
*/!*
|
||||||
|
@ -295,7 +302,7 @@ loadUrl('/article/promise/user.json')
|
||||||
.then(user => {
|
.then(user => {
|
||||||
console.log(user);
|
console.log(user);
|
||||||
*!*
|
*!*
|
||||||
return loadUrl(`https://api.github.com/users/${user.name}`);
|
return httpGet(`https://api.github.com/users/${user.name}`);
|
||||||
*/!*
|
*/!*
|
||||||
})
|
})
|
||||||
*!*
|
*!*
|
||||||
|
@ -319,7 +326,7 @@ loadUrl('/article/promise/user.json')
|
||||||
Самое главное в этом коде -- последовательность вызовов:
|
Самое главное в этом коде -- последовательность вызовов:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
loadUrl(...)
|
httpGet(...)
|
||||||
.then(...)
|
.then(...)
|
||||||
.then(...)
|
.then(...)
|
||||||
.then(...)
|
.then(...)
|
||||||
|
@ -327,7 +334,7 @@ loadUrl(...)
|
||||||
|
|
||||||
При чейнинге, то есть последовательных вызовах `.then...then..then`, в каждый следующий `then` переходит результат от предыдущего.
|
При чейнинге, то есть последовательных вызовах `.then...then..then`, в каждый следующий `then` переходит результат от предыдущего.
|
||||||
|
|
||||||
**Причём, если `then` вернул промис, то в следующий `then` будет передан не сам промис, а его результат.**
|
**Причём, если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
|
||||||
|
|
||||||
В коде выше:
|
В коде выше:
|
||||||
|
|
||||||
|
@ -341,22 +348,26 @@ loadUrl(...)
|
||||||
|
|
||||||
<img src="promiseUserFlow.png">
|
<img src="promiseUserFlow.png">
|
||||||
|
|
||||||
|
Значком "песочные часы" помечены периоды ожидания. Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки ждёт.
|
||||||
|
|
||||||
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис.
|
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис.
|
||||||
|
|
||||||
Это общее правило. Если внутри `then` стартует новый асинхронный процесс, то для того, чтобы дождаться его окончания, мы должны вернуть промис, который перейдёт в состояние "выполнен" после `setTimeout`.
|
Это стандартный приём. Если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис, который перейдёт в состояние "выполнен" после `setTimeout`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Строку `(*)` для этого нужно переписать так:
|
Строку `(*)` для этого нужно переписать так:
|
||||||
```js
|
```js
|
||||||
.then(githubUser => {
|
.then(githubUser => {
|
||||||
...
|
...
|
||||||
|
|
||||||
// вместо setTimeout(() => img.remove(), 3000);
|
// вместо setTimeout(() => img.remove(), 3000); (*)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
img.remove();
|
img.remove();
|
||||||
// после таймаута — просто resolve, без результата,
|
// после таймаута — вызов resolve,
|
||||||
// чтобы управление перешло в следующий then
|
// можно без результата, чтобы управление перешло в следующий then
|
||||||
// (или можно передать данные пользователя дальше по цепочке)
|
// (или можно передать данные пользователя дальше по цепочке)
|
||||||
resolve();
|
resolve();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
@ -364,7 +375,7 @@ loadUrl(...)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь, если к цепочке добавится ещё `then`, то он будет вызван после окончания `setTimeout`.
|
Теперь, если к цепочке добавить ещё `then`, то он будет вызван после окончания `setTimeout`.
|
||||||
|
|
||||||
## Перехват ошибок
|
## Перехват ошибок
|
||||||
|
|
||||||
|
@ -387,11 +398,11 @@ loadUrl(...)
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
// в loadUrl обратимся к несуществующей странице
|
// в httpGet обратимся к несуществующей странице
|
||||||
*/!*
|
*/!*
|
||||||
loadUrl('/page-not-exists')
|
httpGet('/page-not-exists')
|
||||||
.then(response => JSON.parse(response))
|
.then(response => JSON.parse(response))
|
||||||
.then(user => loadUrl(`https://api.github.com/users/${user.name}`))
|
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
||||||
.then(githubUser => {
|
.then(githubUser => {
|
||||||
githubUser = JSON.parse(githubUser);
|
githubUser = JSON.parse(githubUser);
|
||||||
|
|
||||||
|
@ -414,142 +425,245 @@ loadUrl('/page-not-exists')
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
В примере выше ошибка возникает в первом же `httpGet`, но `catch` с тем же успехом поймал бы ошибку во втором `httpGet` или в `JSON.parse`.
|
||||||
|
|
||||||
Принцип очень похож на обычный `try..catch`: мы делаем асинхронную цепочку из `.then`, а затем, когда нужно перехватить ошибки, вызываем `.catch(onRejected)`.
|
Принцип очень похож на обычный `try..catch`: мы делаем асинхронную цепочку из `.then`, а затем, когда нужно перехватить ошибки, вызываем `.catch(onRejected)`.
|
||||||
|
|
||||||
|
|
||||||
[smart header="А что после catch?"]
|
[smart header="А что после `catch`?"]
|
||||||
Обработчик `onRejected` получает ошибку и должен обработать её.
|
Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
|
||||||
|
|
||||||
Здесь два варианта развития событий:
|
Здесь два варианта развития событий:
|
||||||
<ol>
|
<ol>
|
||||||
<li>Если ошибка не критичная, то он возвращает значение через `return`, и управление переходит в ближайший `onFulfilled`.</li>
|
<li>Если ошибка не критичная, то обработчик возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.</li>
|
||||||
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `onRejected`.
|
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `.catch(onRejected)`.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
Это также похоже на обычный `try..catch` -- в блоке `catch` ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает `throw`. Существенное отличие -- в том, что промисы асинхронные, поэтому при отсутствии внешнего `.catch` ошибка не "вываливается" в консоль и не "убивает" скрипт.
|
||||||
|
|
||||||
|
Ведь возможно, что новый обработчик `.catch` будет добавлен в цепочку позже.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
## Промисы в деталях
|
## Промисы в деталях
|
||||||
|
|
||||||
Посмотрим внимательнее, что такое промис и как он работает, в соответствии со стандартом ES-2015.
|
Самым основным источником информации по промисам является, разумеется, [стандарт](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-promise-objects).
|
||||||
|
|
||||||
|
Чтобы наше понимание промисов было полным, и мы могли с лёгкостью разрешать сложные ситуации, посмотрим внимательнее, что такое промис и как он работает, но уже не в общих словах, а детально, в соответствии со стандартом EcmaScript.
|
||||||
|
|
||||||
|
Согласно стандарту, у объекта `new Promise(executor)` при создании есть четыре внутренних свойства:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>`PromiseState` -- состояние, вначале "pending".</li>
|
||||||
|
<li>`PromiseResult` -- результат, при создании значения нет.</li>
|
||||||
|
<li>`PromiseFulfillReactions` -- список функций-обработчиков успешного выполнения.</li>
|
||||||
|
<li>`PromiseRejectReactions` -- список функций-обработчиков ошибки.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
Поток выполнения сразу станет очевидным, как только мы разберём очень простое правило.
|
<img src="promiseEcma.png">
|
||||||
|
|
||||||
Оно состоит из трёх частей:
|
Когда функция-executor вызывает reject или resolve, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
|
||||||
|
|
||||||
<ol>
|
Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
|
||||||
<li>Каждый `promise.then/catch` возвращает новый промис `newPromise`.</li>
|
|
||||||
<li>Ссылка на `newPromise` сохраняется в записывается в `promise`.</li>
|
|
||||||
</li>При успешном выполнении этого промиса, вызывается следующий `onFulfilled`, если ошибка, то `onRejected`.</li>
|
|
||||||
<li>Этот обработчик возвращает... см. пункт 1.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Давайте по шагам.
|
Исключение из этого правила -- если `resolve` возвращает другой Promise. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
|
||||||
|
|
||||||
Очевидно, что каждый .then/catch должен промис. Это всегда так, иначе мы бы не смогли организовать чейнинг.
|
Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`.
|
||||||
|
|
||||||
Этот промис -- всегда новый, не равный предыдущему.
|
Он делает следующее:
|
||||||
|
<ul>
|
||||||
|
<li>Если `PromiseState == "pending"`, то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.</li>
|
||||||
|
<li>Иначе обработчики сразу помещаются в очередь на выполнение.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
Проверим это:
|
Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно -- после (выполнятся в ближайшее время, через асинхронную очередь).
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// Создадим промис и цепочку .then
|
|
||||||
var promise = new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => resolve(1), 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
var promise2 = promise
|
|
||||||
.then(alert);
|
|
||||||
|
|
||||||
// Выведем результат then
|
|
||||||
alert(promise2); // да, это промис: [object Promise]
|
|
||||||
alert(promise == promise2); // да, это новый промис
|
|
||||||
```
|
|
||||||
|
|
||||||
[smart header="Обработчики -- всегда асинхронны"]
|
|
||||||
Кстати, обработчики в `.then/catch` всегда выполняются асинхронно, как будто обёрнуты в `setTimeout(..., 0)`, даже если внутри синхронная функция.
|
|
||||||
|
|
||||||
Например:
|
Например:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
var promise = new Promise((resolve, reject) => {
|
// Промис выполнится сразу же
|
||||||
resolve("result")
|
var promise = new Promise((resolve, reject) => resolve(1));
|
||||||
});
|
|
||||||
|
|
||||||
// сработает асинхронно
|
// PromiseState = "resolved"
|
||||||
promise.then(alert); // (2)
|
// PromiseResult = 1
|
||||||
|
|
||||||
alert("before promise"); // (1)
|
// Добавили обработчик к выполненному промису
|
||||||
|
promise.then(alert); // ...он сработает тут же
|
||||||
```
|
```
|
||||||
|
|
||||||
При запуске этого кода выведется сначала `(1)`, а потом `(2)`.
|
Разумеется, можно добавлять и много обработчиков на один и тот же промис:
|
||||||
|
|
||||||
Такая "форсированная" асинхронность предусмотрена стандартом, чтобы избежать ситуаций, когда некий код работает то синхронно, то асинхронно, и возможных при этом багов.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
Обработчики добавляются в общий список.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
При выполнении обработчика `.then/catch` -- он может либо что-то вернуть
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Обработчика вернул значение (или ничего не вернул, что можно считать `return undefined`)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Пример:
|
|
||||||
|
|
||||||
|
|
||||||
Ошибкой считается либо вызов `reject`, либо синхронный `throw` в promise-функции:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var promise = new Promise((resolve, reject) {
|
//+ run
|
||||||
|
// Промис выполнится сразу же
|
||||||
|
var promise = new Promise((resolve, reject) => resolve(1));
|
||||||
|
|
||||||
// эти две строки делают одно и то же:
|
promise.then( function f1(result) {
|
||||||
// переводят промис в состояние "rejected"
|
|
||||||
throw new Error("...");
|
|
||||||
reject(new Error("..."))
|
|
||||||
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Обратим внимание, что `throw` корректно обрабатывается только если он синхронный.
|
|
||||||
|
|
||||||
Вот так -- не подойдёт:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var promise = new Promise((resolve, reject) {
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// нельзя так делать, асинхронно должен быть reject
|
|
||||||
throw new Error("...");
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
На
|
|
||||||
|
|
||||||
|
|
||||||
Мы уже использовали это неявно, при загрузке пользователя в примере выше:
|
|
||||||
|
|
||||||
```js
|
|
||||||
loadUrl('/article/promise/user.json')
|
|
||||||
.then(response => {
|
|
||||||
// ...
|
|
||||||
*!*
|
*!*
|
||||||
let user = JSON.parse(response);
|
alert(result); // 1
|
||||||
|
*/!*
|
||||||
|
return 'f1';
|
||||||
|
})
|
||||||
|
|
||||||
|
promise.then( function f2(result) {
|
||||||
|
*!*
|
||||||
|
alert(result); // 1
|
||||||
|
*/!*
|
||||||
|
return 'f2';
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Вид объекта `promise` после этого:
|
||||||
|
|
||||||
|
<img src="promiseTwo.png">
|
||||||
|
|
||||||
|
На этой иллюстрации можно увидеть, что `.then` если один из обработчиков не указан, добавляет его "от себя", следующим образом:
|
||||||
|
<ul>
|
||||||
|
<li>Для успешного выполнения -- функция `Identity`, которая выглядит как `arg => return arg`, то есть возвращает аргумент без изменений.</li>
|
||||||
|
<li>Для ошибки -- функция `Thrower`, которая выглядит как `arg => throw arg`, то есть генерирует ошибку.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Это, по сути дела, формальность, но без неё некоторые особенности поведения промисов могут "не сойтись" в общую логику, поэтому мы упоминаем о ней здесь.
|
||||||
|
|
||||||
|
Обратим внимание, в этом примере намеренно *не используется чейнинг*. То есть, обработчики добавляются именно на один и тот же промис.
|
||||||
|
|
||||||
|
Поэтому оба `alert` выдадут одно значение `1`. Алгоритм такой -- все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
|
||||||
|
|
||||||
|
**Для того, чтобы результат обработчика передать следующей функции, `.then` создаёт новый промис и возвращает его.**
|
||||||
|
|
||||||
|
В примере выше создаётся два таких промиса, каждый из которых даёт свою ветку выполнения:
|
||||||
|
|
||||||
|
<img src="promiseTwoThen.png">
|
||||||
|
|
||||||
|
Изначально эти новые промисы -- пустые. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Если вернётся обычное значение (не промис), новый промис перейдёт в `"resolved"` с ним.</li>
|
||||||
|
<li>Если был `throw`, то новый промис перейдёт в состояние `"rejected"` с ошибкой.</li>
|
||||||
|
<li>Если вернётся промис, то используем его результат (он может быть как `resolved`, так и `rejected`).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<img src="promiseHandlerVariants.png">
|
||||||
|
|
||||||
|
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
|
||||||
|
|
||||||
|
Первый промис и обработка его результата:
|
||||||
|
|
||||||
|
```js
|
||||||
|
httpGet('/article/promise/user.json')
|
||||||
|
.then(JSON.parse)
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="promiseLoadAvatarChain-1.png">
|
||||||
|
|
||||||
|
|
||||||
|
Если промис завершился через `resolve`, то результат -- в `JSON.parse`, если `reject` -- то в Thrower.
|
||||||
|
|
||||||
|
Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан. Можно сказать, что второй обработчик выглядит так:
|
||||||
|
|
||||||
|
```js
|
||||||
|
*!*
|
||||||
|
function thrower(err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
httpGet('/article/promise/user.json')
|
||||||
|
.then(JSON.parse, thrower)
|
||||||
|
```
|
||||||
|
|
||||||
|
Заметим, что когда обработчик в промисах делает `throw`, то ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
|
||||||
|
|
||||||
|
Добавим в код ещё строку:
|
||||||
|
|
||||||
|
```js
|
||||||
|
httpGet('/article/promise/user.json')
|
||||||
|
.then(JSON.parse)
|
||||||
|
*!*
|
||||||
|
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Цепочка "выросла вниз":
|
||||||
|
|
||||||
|
<img src="promiseLoadAvatarChain-2.png">
|
||||||
|
|
||||||
|
Функция `JSON.parse` либо возвращает объект с данными, либо генерирует ошибку (что расценивается как `reject`).
|
||||||
|
|
||||||
|
Если всё хорошо, то `then(user => httpGet(…))` вернёт новый промис, на который стоят уже два обработчика:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
httpGet('/article/promise/user.json')
|
||||||
|
.then(JSON.parse)
|
||||||
|
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
||||||
|
.then(
|
||||||
|
*!*
|
||||||
|
JSON.parse,
|
||||||
|
function avatarError(error) {
|
||||||
|
if (error.code == 404) {
|
||||||
|
return {name: "NoGithub", avatar_url: '/article/promise/anon.png'};
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
*/!*
|
*/!*
|
||||||
return user;
|
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Если при `JSON.parse` будет синтаксическая ошибка (некорректный JSON), то
|
<img src="promiseLoadAvatarChain-3.png">
|
||||||
|
|
||||||
|
Наконец-то хоть какая-то обработка ошибок!
|
||||||
|
|
||||||
|
Обработчик `avatarError` перехватит ошибки, которые были ранее. Функция `httpGet` при генерации ошибки записывает её HTTP-код в свойство `error.code`, так что мы легко можем понять -- что это:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Если страница на Github не найдена -- можно продолжить выполнение, используя "аватар по умолчанию"</li>
|
||||||
|
<li>Иначе -- пробрасываем ошибку дальше.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Итого, после добавления оставшейся части цепочки, картина получается следующей:
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
httpGet('/article/promise/userNoGithub.json')
|
||||||
|
.then(JSON.parse)
|
||||||
|
.then(user => loadUrl(`https://api.github.com/users/${user.name}`))
|
||||||
|
.then(
|
||||||
|
JSON.parse,
|
||||||
|
function githubError(error) {
|
||||||
|
if (error.code == 404) {
|
||||||
|
return {name: "NoGithub", avatar_url: '/article/promise/anon.png'};
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function showAvatar(githubUser) {
|
||||||
|
let img = new Image();
|
||||||
|
img.src = githubUser.avatar_url;
|
||||||
|
img.className = "promise-avatar-example";
|
||||||
|
document.body.appendChild(img);
|
||||||
|
setTimeout(() => img.remove(), 3000);
|
||||||
|
})
|
||||||
|
.catch(function genericError(error) {
|
||||||
|
alert(error); // Error: Not Found
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="promiseLoadAvatarChain-4.png">
|
||||||
|
|
||||||
|
В конце срабатывает общий обработчик `genericError`, который перехватывает любые ошибки. В данном случае ошибки, которые в него попадут, уже носят критический характер, что-то серьёзно не так. Чтобы посетитель не удивился отсутствию информации, мы показываем ему сообщение об этом.
|
||||||
|
|
||||||
|
Можно и как-то иначе вывести уведомление о проблеме, главное -- не забыть обработать ошибки в конце. Если последнего `catch` не будет, а цепочка завершится с ошибкой, то посетитель об этом не узнает.
|
||||||
|
|
||||||
|
В консоли тоже ничего не будет, так как ошибка остаётся "внутри" промиса, ожидая добавления следующего обработчика `onRejected`, которому будет передана.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
1-js/10-es-modern/3-promise/promiseEcma.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
1-js/10-es-modern/3-promise/promiseEcma@2x.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
1-js/10-es-modern/3-promise/promiseHandlerVariants.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
1-js/10-es-modern/3-promise/promiseHandlerVariants@2x.png
Normal file
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-1.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-1@2x.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-2.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-2@2x.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-3.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-3@2x.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-4.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain-4@2x.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
1-js/10-es-modern/3-promise/promiseLoadAvatarChain@2x.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
1-js/10-es-modern/3-promise/promiseTwo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
1-js/10-es-modern/3-promise/promiseTwo@2x.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
1-js/10-es-modern/3-promise/promiseTwoThen.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
1-js/10-es-modern/3-promise/promiseTwoThen@2x.png
Normal file
After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
4
1-js/10-es-modern/3-promise/user-no-guthub.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "an-unknown-person-32662",
|
||||||
|
"isAdmin": false
|
||||||
|
}
|
4
1-js/10-es-modern/3-promise/userNoGithub.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "an-unknown-person-32662",
|
||||||
|
"isAdmin": false
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
# Современные возможности ES-2015
|
# Современные возможности ES-2015 [в работе]
|
||||||
|
|
||||||
Современный стандарт ES-2015 и его расширения для JavaScript.
|
Современный стандарт ES-2015 и его расширения для JavaScript.
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |