diff --git a/1-js/10-es-modern/10-set-map/article.md b/1-js/10-es-modern/10-set-map/article.md
index f255d860..f7f3f51c 100644
--- a/1-js/10-es-modern/10-set-map/article.md
+++ b/1-js/10-es-modern/10-set-map/article.md
@@ -1,12 +1,12 @@
# Set, Map, WeakSet и WeakMap
-Новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
+В ES-2015 появились новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
## Map
-`Map` -- коллекция для хранения записей вида `ключ: значение`.
+`Map` -- коллекция для хранения записей вида `ключ:значение`.
В отличие от объектов, в которых ключами могут быть только строки, в `Map` ключом может быть произвольное значение, например:
@@ -16,23 +16,32 @@
let map = new Map();
-map.set('1', 'str1'); // строка
-map
- .set(1, 'num1') // число
- .set(true, 'bool1'); // булевое
+map.set('1', 'str1'); // ключ-строка
+map.set(1, 'num1'); // число
+map.set(true, 'bool1'); // булевое значение
-// в обычном объекте это было бы одно и то же
+// в обычном объекте это было бы одно и то же,
+// map сохраняет тип ключа
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
```
-Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`, причём `set` можно чейнить. И ключи и значения сохраняются "как есть", без преобразований типов.
+Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`. И ключи и значения сохраняются "как есть", без преобразований типов.
Свойство `map.size` хранит общее количество записей в `map`.
-**При создании `Map` можно сразу инициализовать списком значений.**
+Метод `set` можно чейнить:
+
+```js
+map
+ .set('1', 'str1')
+ .set(1, 'num1')
+ .set(true, 'bool1');
+```
+
+При создании `Map` можно сразу инициализовать списком значений.
Объект `map` с тремя ключами, как и в примере выше:
@@ -44,9 +53,9 @@ let map = new Map([
]);
```
-Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив), которые должен возвратить объект с ключами `0`,`1` -- также не обязательно массив. Везде утиная типизация, максимальная гибкость.
+Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив). Везде утиная типизация, максимальная гибкость.
-**В качестве ключей можно использовать и объекты:**
+**В качестве ключей `map` можно использовать и объекты:**
```js
//+ run
@@ -54,6 +63,7 @@ let map = new Map([
let user = { name: "Вася" };
+// для каждого пользователя будем хранить количество посещений
let visitsCountMap = new Map();
*!*
@@ -65,12 +75,12 @@ alert( visitsCountMap.get(user) ); // 123
```
[smart header="Как map сравнивает ключи"]
-Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`.
+Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`. Поэтому значение `NaN` также может быть использовано в качестве ключа.
-Этот алгоритм жёстко фиксирован в стандарте, его нельзя изменять или задавать свою функцию для него.
+Этот алгоритм жёстко фиксирован в стандарте, его нельзя изменять или задавать свою функцию сравнения.
[/smart]
-Для удаления записей:
+Методы для удаления записей:
- `map.delete(key)` удаляет запись с ключом `key`, возвращает `true`, если такая запись была, иначе `false`.
- `map.clear()` -- удаляет все записи, очищает `map`.
@@ -82,22 +92,9 @@ alert( visitsCountMap.get(user) ); // 123
- `map.has(key)` -- возвращает `true`, если ключ есть, иначе `false`.
-Ещё раз заметим, что используемый алгоритм сравнения ключей аналогичен `===`, за исключением `NaN`, которое равно самому себе:
-
-```js
-//+ run
-'use strict';
-
-let map = new Map([ [NaN: 1] ]);
-
-alert( map.has(NaN) ); // true
-alert( map.get(NaN) ); // 1
-alert( map.delete(NaN) ); // true
-```
-
### Итерация
-Для итерации используется один из трёх методов:
+Для итерации по `map` используется один из трёх методов:
- `map.keys()` -- возвращает итерируемый объект для ключей,
- `map.values()` -- возвращает итерируемый объект для значений,
@@ -116,14 +113,17 @@ let recipeMap = new Map([
['сметаны', '50 гр']
]);
+// цикл по ключам
for(let fruit of recipeMap.keys()) {
alert(fruit); // огурцов, помидоров, сметаны
}
+// цикл по значениям [ключ,значение]
for(let amount of recipeMap.values()) {
alert(amount); // 500 гр, 350 гр, 50 гр
}
+// цикл по записям
for(let entry of recipeMap) { // то же что и recipeMap.entries()
alert(entry); // огурцов,500 гр , и т.д., массивы по 2 значения
}
@@ -150,19 +150,11 @@ recipeMap.forEach( (value, key, map) => {
});
```
-
-У `Map` есть и другие свой методы:
-
-
-- `map.size()` -- возвращает количество записей,
-- `map.clear()` -- удаляет все записи.
-
-
## Set
`Set` -- коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз.
-Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. Повторные визиты не должны приводить к дубликатам.
+Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. При этом повторные визиты не должны приводить к дубликатам, то есть каждого посетителя нужно "посчитать" ровно один раз.
`Set` для этого отлично подходит:
@@ -176,13 +168,14 @@ let vasya = {name: "Вася"};
let petya = {name: "Петя"};
let dasha = {name: "Даша"};
-// посещения
+// посещения, некоторые пользователи заходят много раз
set.add(vasya);
set.add(petya);
set.add(dasha);
set.add(vasya);
set.add(petya);
+// set сохраняет только уникальные значения
alert( set.size ); // 3
set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
@@ -190,12 +183,12 @@ set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
В примере выше многократные добавления одного и того же объекта в `set` не создают лишних копий.
-Альтернатива `Set` -- это массивы с поиском дубликата при каждом добавлении, но это гораздо хуже по производительности. Или же объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный `Set`.
+Альтернатива `Set` -- это массивы с поиском дубликата при каждом добавлении, но они гораздо хуже по производительности. Или можно использовать обычные объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный `Set`.
Основные методы:
- `set.add(item)` -- добавляет в коллекцию `item`, возвращает `set` (чейнится).
-- `set.delete(item) -- удаляет `item` из коллекции, возвращает `true`, если он там был, иначе `false`.
+- `set.delete(item)` -- удаляет `item` из коллекции, возвращает `true`, если он там был, иначе `false`.
- `set.has(item)` -- возвращает `true`, если `item` есть в коллекции, иначе `false`.
- `set.clear()` -- очищает `set`.
@@ -214,17 +207,17 @@ set.forEach((value, valueAgain, set) => {
});
```
-Заметим, что в `Map` у функции в `.forEach` три аргумента: ключ, значение, объект `map`.
+Заметим, что в `Set` у функции в `.forEach` три аргумента: значение, ещё раз значение, и затем сам перебираемый объект `set`. При этом значение повторяется в аргументах два раза.
-В `Set` для совместимости с `Map` сделано похожим образом -- у `.forEach`-функции также три аргумента. Но первые два всегда совпадают и содержат очередное значение множества.
+Так сделано для совместимости с `Map`, где у `.forEach`-функции также три аргумента. Но в `Set` первые два всегда совпадают и содержат очередное значение множества.
## WeakMap и WeakSet
-`WeakSet` -- особый вид `Set` не препятствующий сборщику мусора. То же самое -- `WeakMap` для `Map`.
+`WeakSet` -- особый вид `Set` не препятствующий сборщику мусора удалять свои элементы. То же самое -- `WeakMap` для `Map`.
То есть, если некий объект присутствует только в `WeakSet/WeakMap` -- он удаляется из памяти.
-Это нужно для тех ситуаций, когда сами объекты используются где-то в другом месте кода, а здесь мы хотим хранить для них "вспомогательные" данные, существующие лишь пока жив объект.
+Это нужно для тех ситуаций, когда основное место для хранения и использования объектов находится где-то в другом месте кода, а здесь мы хотим хранить для них "вспомогательные" данные, существующие лишь пока жив объект.
Например, у нас есть элементы на странице или, к примеру, пользователи, и мы хотим хранить для них вспомогательную инфомацию, например обработчики событий или просто данные, но действительные лишь пока объект, к которому они относятся, существует.
@@ -260,6 +253,8 @@ activeUsers.splice(0, 1); // Петя более не активный поль
// weakMap теперь содержит только 1 элемент
```
+Таким образом, `WeakMap` избавляет нас от необходимости вручную удалять вспомогательные данные, когда удалён основной объект.
+
У WeakMap есть ряд ограничений:
- Нет свойства `size`.
@@ -269,7 +264,7 @@ activeUsers.splice(0, 1); // Петя более не активный поль
Иными словами, `WeakMap` работает только на запись (`set`, `delete`) и чтение (`get`, `has`) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое `WeakMap`, нет соответствующих методов.
-Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
+Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. В равной степени он не гарантирует и обратное. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
Поэтому содержимое `WeakMap` в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации `WeakMap`, и связано отсутствие методов, осуществляющих доступ ко всем записям.
@@ -286,22 +281,4 @@ activeUsers.splice(0, 1); // Петя более не активный поль
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/1-js/10-es-modern/15-promise/1-promise-settimeout/solution.md b/1-js/10-es-modern/11-promise/1-promise-settimeout/solution.md
similarity index 100%
rename from 1-js/10-es-modern/15-promise/1-promise-settimeout/solution.md
rename to 1-js/10-es-modern/11-promise/1-promise-settimeout/solution.md
diff --git a/1-js/10-es-modern/15-promise/1-promise-settimeout/task.md b/1-js/10-es-modern/11-promise/1-promise-settimeout/task.md
similarity index 100%
rename from 1-js/10-es-modern/15-promise/1-promise-settimeout/task.md
rename to 1-js/10-es-modern/11-promise/1-promise-settimeout/task.md
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/solution.md b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.md
new file mode 100644
index 00000000..4b787725
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.md
@@ -0,0 +1,27 @@
+Для последовательной загрузки нужно организовать промисы в цепочку, чтобы они выполнялись строго -- один после другого.
+
+Вот код, который это делает:
+
+```js
+// начало цепочки
+let chain = Promise.resolve();
+
+let results = [];
+
+// в цикле добавляем задачи в цепочку
+urls.forEach(function(url) {
+ chain = chain
+ .then(() => httpGet(url))
+ .then((result) => {
+ results.push(result);
+ });
+});
+
+// в конце — выводим результаты
+chain.then(() => {
+ alert(results);
+});
+```
+
+Использование `Promise.resolve()` как начала асинхронной цепочки -- очень распространённый приём.
+
diff --git a/1-js/10-es-modern/15-promise/guest.json b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/guest.json
similarity index 100%
rename from 1-js/10-es-modern/15-promise/guest.json
rename to 1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/guest.json
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/httpGet.js b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/httpGet.js
new file mode 100644
index 00000000..d7a16a96
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/httpGet.js
@@ -0,0 +1,25 @@
+function httpGet(url) {
+
+ return new Promise(function(resolve, reject) {
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+
+ xhr.onload = function() {
+ if (this.status == 200) {
+ resolve(this.response);
+ } else {
+ var error = new Error(this.statusText);
+ error.code = this.status;
+ reject(error);
+ }
+ };
+
+ xhr.onerror = function() {
+ reject(new Error("Network Error"));
+ };
+
+ xhr.send();
+ });
+
+}
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/index.html b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/index.html
new file mode 100644
index 00000000..18559232
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-js/10-es-modern/15-promise/user.json b/1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/user.json
similarity index 100%
rename from 1-js/10-es-modern/15-promise/user.json
rename to 1-js/10-es-modern/11-promise/2-promise-sequence/solution.view/user.json
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/guest.json b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/guest.json
new file mode 100644
index 00000000..c32ac845
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/guest.json
@@ -0,0 +1,4 @@
+{
+ "name": "guest",
+ "isAdmin": false
+}
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/httpGet.js b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/httpGet.js
new file mode 100644
index 00000000..d7a16a96
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/httpGet.js
@@ -0,0 +1,25 @@
+function httpGet(url) {
+
+ return new Promise(function(resolve, reject) {
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+
+ xhr.onload = function() {
+ if (this.status == 200) {
+ resolve(this.response);
+ } else {
+ var error = new Error(this.statusText);
+ error.code = this.status;
+ reject(error);
+ }
+ };
+
+ xhr.onerror = function() {
+ reject(new Error("Network Error"));
+ };
+
+ xhr.send();
+ });
+
+}
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/index.html b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/index.html
new file mode 100644
index 00000000..fa875c5b
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-js/10-es-modern/17-generator/user.json b/1-js/10-es-modern/11-promise/2-promise-sequence/source.view/user.json
similarity index 100%
rename from 1-js/10-es-modern/17-generator/user.json
rename to 1-js/10-es-modern/11-promise/2-promise-sequence/source.view/user.json
diff --git a/1-js/10-es-modern/11-promise/2-promise-sequence/task.md b/1-js/10-es-modern/11-promise/2-promise-sequence/task.md
new file mode 100644
index 00000000..a75b8d9a
--- /dev/null
+++ b/1-js/10-es-modern/11-promise/2-promise-sequence/task.md
@@ -0,0 +1,26 @@
+
+# Загрузить массив последовательно
+
+Есть массив URL:
+
+```js
+//+ run
+'use strict';
+
+let urls = [
+ 'user.json',
+ 'guest.json'
+];
+```
+
+Напишите код, который все URL из этого массива загружает -- один за другим (последовательно), и сохраняет в результаты в массиве `results`, а потом выводит.
+
+Вариант с параллельной загрузкой выглядел бы так:
+
+```js
+Promise.all( urls.map(httpGet) )
+ .then(alert);
+```
+
+В этой задаче загрузку нужно реализовать последовательно.
+
diff --git a/1-js/10-es-modern/15-promise/anon.png b/1-js/10-es-modern/11-promise/anon.png
similarity index 100%
rename from 1-js/10-es-modern/15-promise/anon.png
rename to 1-js/10-es-modern/11-promise/anon.png
diff --git a/1-js/10-es-modern/15-promise/article.md b/1-js/10-es-modern/11-promise/article.md
similarity index 76%
rename from 1-js/10-es-modern/15-promise/article.md
rename to 1-js/10-es-modern/11-promise/article.md
index a4916eae..3b4ef97c 100644
--- a/1-js/10-es-modern/15-promise/article.md
+++ b/1-js/10-es-modern/11-promise/article.md
@@ -39,7 +39,7 @@ var promise = new Promise(function(resolve, reject) {
Универсальный метод для навешивания обработчиков:
-```
+```js
promise.then(onFulfilled, onRejected)
```
@@ -51,9 +51,9 @@ promise.then(onFulfilled, onRejected)
С его помощью можно назначить как оба обработчика сразу, так и только один:
```js
-// только на успешное выполнение
+// onFulfilled сработает при успешном выполнении
promise.then(onFulfilled)
-// только на ошибку
+// onRejected сработает при ошибке
promise.then(null, onRejected)
```
@@ -65,7 +65,9 @@ promise.then(null, onRejected)
Если в функции промиса происходит синхронный `throw` (или иная ошибка), то вызывается `reject`:
```js
//+ run
-var p = new Promise((resolve, reject) => {
+'use strict';
+
+let p = new Promise((resolve, reject) => {
// то же что reject(new Error("o_O"))
throw new Error("o_O");
})
@@ -83,8 +85,10 @@ p.catch(alert); // Error: o_O
```js
//+ run
+'use strict';
+
// Создаётся объект promise
-var promise = new Promise((resolve, reject) => {
+let promise = new Promise((resolve, reject) => {
setTimeout(() => {
// переведёт промис в состояние fulfilled с результатом "result"
@@ -135,7 +139,7 @@ promise
```
-Конечно, вместо `setTimeout` мог бы быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс.
+Конечно, вместо `setTimeout` внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал `resolve` или `reject`, которые передадут результат обработчикам.
[smart header="Только один аргумент"]
Функции `resolve/reject` принимают ровно один аргумент -- результат/ошибку.
@@ -153,7 +157,9 @@ promise
```js
//+ run
-var promise = new Promise((resolve, reject) => {
+'use strict';
+
+let promise = new Promise((resolve, reject) => {
*!*
// через 1 секунду готов результат: result
@@ -184,7 +190,9 @@ promise
```js
//+ run
-var promise = new Promise((resolve, reject) => {
+'use strict';
+
+let promise = new Promise((resolve, reject) => {
// reject вызван раньше, resolve будет проигнорирован
setTimeout(() => reject(new Error("error")), 1000);
@@ -262,7 +270,7 @@ httpGet("/article/promise/user.json")
[smart header="Метод `fetch`"]
-Заметим, что ряд современных браузеров уже поддерживает [fetch](/fetch) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он, конечно, гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
+Заметим, что ряд современных браузеров уже поддерживает [fetch](/fetch) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
[/smart]
@@ -332,15 +340,15 @@ httpGet(...)
.then(...)
```
-При чейнинге, то есть последовательных вызовах `.then...then..then`, в каждый следующий `then` переходит результат от предыдущего.
+При чейнинге, то есть последовательных вызовах `.then…then…then`, в каждый следующий `then` переходит результат от предыдущего. Вызовы `console.log` оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.
-**Причём, если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
+**Если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
В коде выше:
- В первом `then` возвращается объект `user`, он переходит в следующий `then`.
-- Во втором `then` возвращается промис (результат `loadUser`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.
+- Во втором `then` возвращается промис (результат нового вызова `httpGet`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.
- Третий `then` ничего не возвращает.
@@ -348,13 +356,19 @@ httpGet(...)
-Значком "песочные часы" помечены периоды ожидания. Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки ждёт.
+Значком "песочные часы" помечены периоды ожидания, которых всего два: в исходном `httpGet` и в подвызове далее по цепочке.
-Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис.
+Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки будет ждать.
-Это стандартный приём. Если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис, который перейдёт в состояние "выполнен" после `setTimeout`.
+То есть, логика довольно проста:
+
+- В каждом `then` мы получаем текущий результат работы.
+- Можно его обработать синхронно и вернуть результат (например, применить `JSON.parse`). Или же, если нужна асинхронная обработка -- инициировать её и вернуть промис.
+
+Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис. Это общее правило: если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
+В данном случае промис должен перейти в состояние "выполнен" после срабатывания `setTimeout`.
Строку `(*)` для этого нужно переписать так:
```js
@@ -369,7 +383,9 @@ httpGet(...)
// после таймаута — вызов resolve,
// можно без результата, чтобы управление перешло в следующий then
// (или можно передать данные пользователя дальше по цепочке)
+*!*
resolve();
+*/!*
}, 3000);
});
})
@@ -431,12 +447,12 @@ httpGet('/page-not-exists')
[smart header="А что после `catch`?"]
-Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
+Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
-Здесь два варианта развития событий:
+Есть два варианта развития событий:
-- Если ошибка не критичная, то обработчик возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.
-- Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `.catch(onRejected)`.
+
- Если ошибка не критичная, то `onRejected` возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.
+- Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в следующий ближайший `.catch(onRejected)`.
@@ -462,11 +478,11 @@ httpGet('/page-not-exists')
-Когда функция-executor вызывает reject или resolve, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
+Когда функция-executor вызывает `reject` или `resolve`, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
-Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
+Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
-Исключение из этого правила -- если `resolve` возвращает другой Promise. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
+Исключение из этого правила -- если `resolve` возвращает другой `Promise`. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`.
@@ -519,7 +535,9 @@ promise.then( function f2(result) {
-На этой иллюстрации можно увидеть, что `.then` если один из обработчиков не указан, добавляет его "от себя", следующим образом:
+На этой иллюстрации можно увидеть добавленные нами обработчики `f1`, `f2`, а также -- автоматические добавленные обработчики ошибок `"Thrower"`.
+
+Дело в том, что `.then`, если один из обработчиков не указан, добавляет его "от себя", следующим образом:
- Для успешного выполнения -- функция `Identity`, которая выглядит как `arg => return arg`, то есть возвращает аргумент без изменений.
- Для ошибки -- функция `Thrower`, которая выглядит как `arg => throw arg`, то есть генерирует ошибку.
@@ -529,15 +547,19 @@ promise.then( function f2(result) {
Обратим внимание, в этом примере намеренно *не используется чейнинг*. То есть, обработчики добавляются именно на один и тот же промис.
-Поэтому оба `alert` выдадут одно значение `1`. Алгоритм такой -- все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
+Поэтому оба `alert` выдадут одно значение `1`.
+
+Все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками в рамках одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
+
+Поэтому, чтобы продолжить работу с результатом, используется чейнинг.
**Для того, чтобы результат обработчика передать следующей функции, `.then` создаёт новый промис и возвращает его.**
-В примере выше создаётся два таких промиса, каждый из которых даёт свою ветку выполнения:
+В примере выше создаётся два таких промиса (т.к. два вызова `.then`), каждый из которых даёт свою ветку выполнения:
-Изначально эти новые промисы -- пустые. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
+Изначально эти новые промисы -- "пустые", они ждут. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
- Если вернётся обычное значение (не промис), новый промис перейдёт в `"resolved"` с ним.
@@ -547,6 +569,8 @@ promise.then( function f2(result) {
+Дальше выполнятся уже обработчики на новом промисе, и так далее.
+
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
Первый промис и обработка его результата:
@@ -561,20 +585,16 @@ httpGet('/article/promise/user.json')
Если промис завершился через `resolve`, то результат -- в `JSON.parse`, если `reject` -- то в Thrower.
-Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан. Можно сказать, что второй обработчик выглядит так:
+Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан.
+
+Можно считать, что второй обработчик выглядит так:
```js
-*!*
-function thrower(err) {
- throw err;
-}
-*/!*
-
httpGet('/article/promise/user.json')
- .then(JSON.parse, thrower)
+ .then(JSON.parse, *!*err => throw err*/!*)
```
-Заметим, что когда обработчик в промисах делает `throw`, то ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
+Заметим, что когда обработчик в промисах делает `throw` -- в данном случае, при ошибке запроса, то такая ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
Добавим в код ещё строку:
@@ -663,8 +683,12 @@ httpGet('/article/promise/userNoGithub.json')
В консоли тоже ничего не будет, так как ошибка остаётся "внутри" промиса, ожидая добавления следующего обработчика `onRejected`, которому будет передана.
+Итак, мы рассмотрели основные приёмы использования промисов. Далее -- посмотрим некоторые полезные вспомогательные методы.
+
+## Параллельное выполнение
+
+Что, если мы хотим осуществить несколько асинхронных процессов одновременно и обработать их результат?
-## Вспомогательные методы
В классе `Promise` есть следующие статические методы.
@@ -681,11 +705,44 @@ Promise.all([
httpGet('/article/promise/user.json'),
httpGet('/article/promise/guest.json')
]).then(results => {
- results = results.map(JSON.parse);
- alert( results[0].name + ', ' + results[1].name ); // iliakan, guest
+ alert(results);
});
```
+Допустим, у нас есть массив с URL.
+
+```js
+let urls = [
+ '/article/promise/user.json',
+ '/article/promise/guest.json'
+];
+```
+
+Чтобы загрузить их параллельно, нужно:
+
+- Создать для каждого URL соответствующий промис.
+- Обернуть массив таких промисов в `Promise.all`.
+
+
+Получится так:
+
+```js
+//+ run
+'use strict';
+
+let urls = [
+ '/article/promise/user.json',
+ '/article/promise/guest.json'
+];
+
+*!*
+Promise.all( urls.map(httpGet) )
+*/!*
+ .then(results => {
+ alert(results);
+ });
+```
+
Заметим, что если какой-то из промисов завершился с ошибкой, то результатом `Promise.all` будет эта ошибка. При этом остальные промисы игнорируются.
Например:
@@ -706,7 +763,7 @@ Promise.all([
### Promise.race(iterable)
-Как и `Promise.all` получает итерируемый объект с промисами и возвращает новый промис.
+Вызов `Promise.race`, как и `Promise.all`, получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис.
Но, в отличие от `Promise.all`, результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
@@ -724,7 +781,7 @@ Promise.race([
});
```
-### Promise.resolve(value)
+## Promise.resolve(value)
Вызов `Promise.resolve(value)` создаёт успешно выполнившийся промис с результатом `value`.
@@ -746,7 +803,7 @@ Promise.resolve(window.location) // начать с этого значения
.then(alert) // и вывести результат
```
-### Promise.reject(error)
+## Promise.reject(error)
Аналогично `Promise.resolve(value)` создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой `error`.
@@ -760,7 +817,7 @@ Promise.reject(new Error("..."))
Метод `Promise.reject` используется очень редко, гораздо реже чем `resolve`, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
-### Итого
+## Итого
- Промис -- это специальный объект, который хранит своё состояние, текущий результат (если есть) и коллбэки.
@@ -770,12 +827,9 @@ Promise.reject(new Error("..."))
- Для передачи результата от одного обработчика к другому используется чейнинг.
-В современной JavaScript-разработки промисы в явном виде используются, как ни странно, довольно редко.
-
-Тем не менее, понимать промисы нужно обязательно, так как бывают ситуации, когда без них сложно.
-
-Промисы служат основой для более продвинутых способов написания асинхронного кода, использующих генераторы. Мы рассмотрим их далее в этом разделе.
+У промисов есть некоторые ограничения. В частности, стандарт не предусматривает какой-то метод для "отмены" промиса, хотя в ряде ситуаций (http-запросы) это было бы довольно удобно. Возможно, он появится в следующей версии стандарта JavaScript.
+В современной JavaScript-разработке сложные цепочки с промисами используются редко, так как они куда проще описываются при помощи генераторов с библиотекой `co`, которые рассмотрены в [соответствующей главе](/generator). Можно сказать, что промисы лежат в основе более продвинутых способов асинхронной разработки.
[head]