This commit is contained in:
Ilya Kantor 2015-07-05 19:53:50 +03:00
parent 1551857d03
commit 9a504669d3
33 changed files with 1801 additions and 0 deletions

View file

@ -0,0 +1,227 @@
# Деструктуризация
*Деструктуризация* (destructuring assignment) -- способ присвоить массив или объект сразу нескольким переменным.
## Массив
Пример деструктуризации массива:
```js
'use strict';
let [firstName, lastName] = "Илья Кантор".split(" ");
alert(firstName); // Илья
alert(lastName); // Кантор
```
При таком присвоении первые два значения массива будут помещены в переменные, а последующие -- будут отброшены.
Однако, можно добавить ещё один параметр, который получит "всё остальное", при помощи троеточия ("spread"):
```js
//+ run
'use strict';
let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" ");
alert(firstName); // Юлий
alert(lastName); // Цезарь
alert(rest); // Император,Рима (массив из 2х элементов)
```
Значением `rest` будет массив из оставшихся элементов массива.
## Деструктуризация объекта
Деструктуризация может "мгновенно" разобрать объект по переменным.
Например:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню",
width: 100,
height: 200
};
*!*
let {title, width, height} = menuOptions;
*/!*
alert(`${title} ${width} ${height}`); // Меню 100 200
```
Как видно, свойства автоматически присваиваются соответствующим переменным.
Если хочется присвоить свойство объекта другой переменной, можно указать соответствие через двоеточие:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню",
width: 100,
height: 200
};
*!*
let {width: w, height: h, title} = menuOptions;
*/!*
alert(`${title} ${w} ${h}`); // Меню 100 200
```
В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием.
Если каких-то свойств в объекте нет,можно указать значение по умолчанию через знак равенства `=`, вот так;
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню"
};
*!*
let {width=100, height=200, title} = menuOptions;
*/!*
alert(`${title} ${width} ${height}`); // Меню 100 200
```
Можно и сочетать одновременно двоеточие и равенство:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню"
};
*!*
let {width:w=100, height:h=200, title} = menuOptions;
*/!*
alert(`${title} ${w} ${h}`); // Меню 100 200
```
А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить "остаток"?
Такая возможность планируется в будущем стандарте ES-2016, и выглядеть она будет так:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню",
width: 100,
height: 200
};
*!*
let {title, ...size} = menuOptions;
*/!*
// title = "Меню"
// size = { width: 100, height: 200} (остаток)
```
Ещё раз заметим, что аналогичный массивам оператор `...` ("spread") для объектов пока не стандарт.
Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями.
[smart header="Деструктуризация без объявления"]
В примерах выше переменные объявлялись прямо перед присваиванием. Конечно, можно использовать и уже существующие переменные.
Однако, для объектов есть небольшой "подвох". В JavaScript, если в основном потоке кода (не внутри другого выражения) встречается `{...}`, то это воспринимается как блок.
Например, можно использовать для ограничения видимости переменных:
```js
//+ run
'use strict';
{
let a = 5;
alert(a); // 5
}
alert(a); // ошибка нет такой переменной
```
...Но в данном случае это создаст проблему при деструктуризации:
```js
let a, b;
{a, b} = {a:5, b:6}; // будет ошибка, оно посчитает, что {a,b} - блок
```
Чтобы избежать интерпретации `{a, b}` как блока, нужно обернуть всё присваивание в скобки:
```js
let a, b;
({a, b} = {a:5, b:6}); // внутри выражения это уже не блок
```
[/smart]
## Вложенные деструктуризации
Деструктуризации можно как угодно сочетать и вкладывать друг в друга.
Пример с подобъектом и подмассивом:
```js
//+ run
'use strict';
let menuOptions = {
size: {
width: 100,
height: 200
},
items: ["Пончик", "Пирожное"]
}
let {
title="Меню",
size: {width, height},
items: [item1, item2]
} = menuOptions;
// Меню 100 200 Пончик Пирожное
alert(`${title} ${width} ${height} ${item1} ${item2}`);
```
## Итого
<ul>
<li>Деструктуризация позволяет разбивать объект или массив на переменные при присвоении.</li>
<li>Синтаксис:
```js
let {prop : varName = default, ...} = object
let [var1, var2, ...rest] = array
```
Здесь двоеточие `:` задаёт отображение свойства в переменную, а `=` задаёт значение по умолчанию (если нужно).
Объявление переменной вначале не обязательно, если переменные уже есть, но без него при деструктуризации объекта может потребоваться обернуть выражение в скобки.
</li>
<li>Сложные объекты и массивы тоже работают, деструктуризации можно вкладывать.</li>
</ul>
Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций.

View file

@ -0,0 +1,414 @@
# Функции
В функциях основные изменения касаются передачи параметров, плюс введена дополнительная короткая запись через стрелочку `=>`.
## Параметры по умолчанию
Можно указывать параметры по умолчанию через равенство `=`, например:
```js
//+ run
function showMenu(title = "Без заголовка", width = 100, height = 200) {
alert(`${title} ${width} ${height}`);
}
showMenu("Меню"); // Меню 100 200
```
Параметр по умолчанию используется при отсутствующем аргументе или равном `undefined`, например:
```js
//+ run
function showMenu(title = "Заголовок", width = 100, height = 200) {
alert(`title=${title} width=${width} height=${height}`);
}
// По умолчанию будут взяты 1 и 3 параметры
// title=Заголовок width=null height=200
showMenu(undefined, null);
```
При передаче любого значения, кроме `undefined`, включая пустую строку, ноль или `null`, параметр считается переданным, и значение по умолчание не используется.
**Параметры по умолчанию могут быть не только значениями, но и выражениями.**
Например:
```js
//+ run
function sayHi(who = getCurrentUser().toUpperCase()) {
alert(`Привет, ${who}!`);
}
function getCurrentUser() {
return 'Вася';
}
sayHi(); // Привет, ВАСЯ!
```
Заметим, что значение выражения `getCurrentUser().toUpperCase()` будет вычислено, и соответствующие функции вызваны -- лишь в том случае, если это необходимо, то есть когда функция вызвана без параметра.
В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция `getCurrentUser()` будет вызвана именно в последней строке, так как не передан параметр.
## Оператор spread вместо arguments
Чтобы получить массив аргументов, можно использовать оператор `…`, например:
```js
//+ run
function showName(firstName, lastName, *!*...rest*/!*) {
alert(`${firstName} ${lastName} - ${rest}`);
}
// выведет: Юлий Цезарь - Император,Рима
showName("Юлий", "Цезарь", "Император", "Рима");
```
В `rest` попадёт массив всех аргументов, начиная со второго.
Заметим, что `rest` -- настоящий массив, с методами `map`, `forEach` и другими, в отличие от `arguments`.
[warn header="Оператор … должен быть в конце"]
Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла:
```js
function f(arg1, ...rest, arg2) {
// будет ошибка
}
```
[/warn]
Выше мы увидели использование `...` для чтения параметров внутри функции. Но этот же оператор можно использовать и для передачи массива параметров как списка, например:
```js
//+ run
'use strict';
let numbers = [2, 3, 15];
// Передаст массив как список аргументов: Math.max(2, 3, 15)
let max = Math.max(*!*...numbers*/!*);
alert( max ); // 15
```
Эти два вызова делают одно и то же:
```js
Math.max(...numbers);
Math.max.apply(Math, numbers);
```
## Деструктуризация в параметрах
Если функция получает объект, то она может его тут же разбить в переменные:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню",
width: 100,
height: 200
};
*!*
function showMenu({title, width, height}) {
*/!*
alert(`${title} ${width} ${height}`); // Меню 100 200
}
showMenu(menuOptions);
```
Можно использовать и более сложную деструктуризацию, с соответствиями и значениями по умолчанию:
```js
//+ run
'use strict';
let menuOptions = {
title: "Меню"
};
*!*
function showMenu({title="Заголовок", width:w=100, height:h=200} = {}) {
*/!*
alert(`${title} ${w} ${h}`);
}
showMenu(menuOptions); // Меню 100 200
showMenu(); // Заголовок 100 200
```
## Имя "name"
В свойстве `name` у функции находится её имя.
Например:
```js
//+ run
'use strict';
function f() {} // f.name == "f"
let g = function g() {}; // g.name == "g"
alert(`${f.name} ${g.name}`) // f g
```
В примере выше показаны Function Declaration и Named Function Expression.
Но современный JavaScript идёт дальше, он старается даже анонимным функциям дать разумные имена.
Например, при создании анонимной функции с одновременной записью в переменную или свойство -- её имя равно названию переменной (или свойства):
```js
'use strict';
let g = function() {}; // g.name == "g"
let user = {
sayHi: function() { }; // user.sayHi.name == "sayHi"
}
```
## Функции в блоке
Объявление функции Function Declaration, сделанное в блоке, видно только в этом блоке.
Например:
```js
//+ run
'use strict';
if (true) {
sayHi(); // работает
function sayHi() {
alert("Привет!");
}
}
sayHi(); // ошибка, функции не существует
```
То есть, иными словами, такое объявление -- ведёт себя так же как `let sayHi = function ...`, сделанное в начале блока.
## Функции через =>
Появился новый синтаксис для задания функций через "стрелку" `=>`.
Его простейший вариант выглядит так:
```js
//+ run
'use strict';
*!*
let inc = x => x+1;
*/!*
alert( inc(1) ); // 2
```
То есть, слева от `=>` находится аргумент, а справа -- выражение, которое нужно вернуть.
Эти две записи -- примерно аналогичны:
```js
let inc = x => x+1;
let inc = function(x) { return x + 1; };
```
Если аргументов несколько, они оборачиваются в скобки, например:
```js
//+ run
'use strict';
let sum = (a,b) => a + b;
alert( sum(1, 2) ); // 3
```
...А если совсем нет аргументов, но функцию хочется задать, то используются пустые скобки:
```js
//+ run
'use strict';
let getTime = () =>
`${new Date().getHours()} : ${new Date().getMinutes()}`;
alert( getTime() ); // текущее время
```
Когда тело функции достаточно большое, то можно его обернуть в `{…}`:
```js
//+ run
'use strict';
let getTime = () => {
let date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
return `${hours}:${minutes}`;
};
alert( getTime() ); // текущее время
```
Заметим, что как только тело функции оборачивается в `{…}`, то оно уже автоматически ничего не возвращает. Нужно делать явный `return`, как в примере выше.
Функции-стрелки очень удобны в качестве коллбеков, например:
```js
//+ run
`use strict`;
let arr = [5, 8, 3];
let sorted = arr.sort( (a,b) => a - b );
alert(sorted); // 3, 5, 8
```
## Функции-стрелки не имеют своего this
Внутри `this` -- тот же, что и снаружи.
Это очень удобно в обработчиках событий и коллбэках, например:
```js
//+ run
'use strict';
let group = {
title: "Наш курс",
students: ["Вася", "Петя", "Даша"],
showList: function() {
*!*
this.students.forEach(
(student) => alert(`${this.title}: ${student}`)
)
*/!*
}
}
group.showList();
// Наш курс: Вася
// Наш курс: Петя
// Наш курс: Даша
```
Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` внутри -- это `group.title`.
Если бы была обычная функция, то была бы ошибка:
```js
//+ run
'use strict';
let group = {
title: "Наш курс",
students: ["Вася", "Петя", "Даша"],
showList: function() {
*!*
this.students.forEach(function(student) {
alert(`${this.title}: ${student}`); // будет ошибка
})
*/!*
}
}
group.showList();
```
При запуске будет "попытка прочитать свойство `title` у `undefined`", так как `.forEach(f)` при запуске `f` не ставит `this`.
...Вместе с тем, отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора.
## Функции-стрелки не имеют своего arguments
В качестве `arguments` используются аргументы внешней "обычной" функции.
Например:
```js
//+ run
'use strict';
function f() {
let showArg = () => alert(arguments[0]);
showArg();
}
f(1); // 1
```
Выведется `1` из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции.
Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.
Например, декоратор `defer(f, ms)` ниже получает функцию `f` и возвращает обёртку вокруг неё, откладывающую вызов на `ms` миллисекунд:
```js
//+ run
'use strict';
*!*
function defer(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms)
}
}
*/!*
function sayHi(who) {
alert(`Привет, ${who}!`);
}
let sayHiDeferred = defer(sayHi, 2000);
sayHiDeferred("Вася"); // Привет, Вася! через 2 секунды
```
Аналогичная реализация без функции-стрелки выглядела бы так:
```js
function defer(f, ms) {
return function() {
let args = arguments;
let ctx = this;
setTimeout(function() {
return f.apply(ctx, args);
}, ms);
}
}
```

View file

@ -0,0 +1,320 @@
# Строки
Есть ряд улучшений и новых методов для строк.
Начнём с, пожалуй, самого важного.
## Строки-шаблоны
Добавлен новый вид кавычек для строк:
```js
let str = `обратные кавычки`;
```
Основные отличия от `"…"` и `'…'`:
<ul>
<li>**В них разрешён перевод строки.**
Например:
```js
//+ run
alert(`моя
многострочная
строка`);
```
Заметим, что пробелы и, собственно, перевод строки также входят в строку, и будут выведены.
</li>
<li>**Можно вставлять выражения при помощи `${…}`.**
Например:
```js
//+ run
'use strict';
let apples = 2;
let oranges = 3;
alert(`${apples} + ${oranges} = ${apples + oranges}`); // 2 + 3 = 5
```
Как видно, можно вставлять как и значение переменной, так и более сложные выражения, вызовы функций и т.п. Это называют "интерполяцией".
</li>
</ul>
## Функции шаблонизации
Можно использовать свою функцию шаблонизации для строк.
Название этой функции ставится перед первой обратной кавычкой:
```js
let str = func`моя строка`;
```
Эта функция будет автоматически вызвана и получит в качестве аргументов строку, разбитую по вхождениям параметров `${…}` и сами эти параметры.
Например:
```js
//+ run
'use strict';
function f(strings, ...values) {
alert(JSON.stringify(strings)); // ["Sum of "," + "," =\n ","!"]
alert(JSON.stringify(strings.raw)); // ["Sum of "," + "," =\\n ","!"]
alert(JSON.stringify(values)); // [3,5,8]
}
let apples = 3;
let oranges = 5;
// | s[0] | v[0] |s[1]| v[1] |s[2] | v[2] |s[3]
let str = f`Sum of ${apples} + ${oranges} =\n ${apples + oranges}!`;
```
В примере выше видно, что строка разбивается по очереди на части: "кусок строки" -- "параметр" -- "кусок строки" -- "параметр".
<ul>
<li>Участки строки идут в первый аргумент-массив `strings`.</li>
<li>У этого массива есть дополнительное свойство `strings.raw`. В нём находятся строки в точности как в оригинале. Это влияет на спец-символы, например в `strings` символ `\n` -- это перевод строки, а в `strings.raw` -- это именно два символа `\n`.</li>
<li>Дальнейший список аргументов функции шаблонизации -- это значения выражений в `${...}`, в данном случае их три.</li>
</ul>
[smart header="Зачем `strings.raw`?"]
В отличие от `strings`, в `strings.raw` содержатся участки строки в "изначально введённом" виде.
То есть, если в строке находится `\n` или `\u1234` или другое особое сочетание символов, то оно таким и останется.
Это нужно в тех случаях, когда функция шаблонизации хочет произвести обработку полностью самостоятельно (свои спец. символы?). Или же когда обработка спец. символов не нужна -- например, строка содержит "обычный текст", набранный непрограммистом без учёта спец. символов.
[/smart]
Функция может как-то преобразовать строку и вернуть новый результат.
В простейшем случае можно просто "склеить" полученные фрагменты в строку:
```js
//+ run
'use strict';
// str восстанавливает строку
function str(strings, ...values) {
let str = "";
for(let i=0; i<values.length; i++) {
str += strings[i];
str += values[i];
}
// последний кусок строки
str += strings[strings.length-1];
return str;
}
let apples = 3;
let oranges = 5;
// Sum of 3 + 5 = 8!
alert( str`Sum of ${apples} + ${oranges} = ${apples + oranges}!`);
```
Функция `str` в примере выше делает то же самое, что обычные обратные кавычки. Но, конечно, можно пойти намного дальше.
Например, генерировать из HTML-строки DOM-узлы (функции шаблонизации не обязательно возвращать именно строку).
Или можно реализовать интернационализацию. В примере ниже функция `i18n` подбирает по строке вида `"Hello, ${name}!"` шаблон перевода `"Привет, {0}!"` (где `{0}` -- место для вставки параметра) и возвращает переведённый результат.
```js
//+ run
'use strict';
let messages = {
"Hello, {0}!": "Привет, {0}!"
};
function i18n(strings, ...values) {
// По форме строки получим шаблон для поиска в messages
// На месте каждого из значений будет его номер: {0}, {1}, …
let pattern = "";
for(let i=0; i<values.length; i++) {
pattern += strings[i] + '{' + i + '}';
}
pattern += strings[strings.length-1];
// Теперь pattern = "Hello, {0}!"
let translated = messages[pattern]; // "Привет, {0}!"
// Заменит в "Привет, {0}" цифры вида {num} на values[num]
return translated.replace(/\{(\d)\}/g, (s, num) => values[num]);
}
let name = "Вася";
// Перевести строку
alert( i18n`Hello, ${name}!` ); // Привет, Вася!
```
Разумеется, эту функцию можно улучшить и расширить. Функция шаблонизации -- это своего рода "стандартный синтаксический сахар" для упрощения форматирования и парсинга строк.
## Улучшена поддержка юникода
Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта.
Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта), такое сочетание называют "суррогатной парой".
Самый частый пример суррогатной пары, который можно встретить в литературе -- это китайские иероглифы.
Заметим, однако, что не всякий китайский иероглиф -- суррогатная пара. Существенная часть "основного" юникод-диапазона как раз отдана под китайский язык, поэтому некоторые иероглифы -- которые в неё "влезли" -- представляются одним юникод-символом, а те, которые не поместились (реже используемые) -- двумя.
Например:
```js
//+ run
alert( '我'.length ); // 1
alert( '𩷶'.length ); // 2
```
В тексте выше для первого иероглифа есть отдельный юникод-символ, и поэтому длина строки `1`, а для второго используется суррогатная пара. Соответственно, длина -- `2`.
Китайскими иероглифами суррогатные пары, естественно, не ограничиваются.
Ими представлены редкие математические символы, а также некоторые символы для эмоций, к примеру:
```js
//+ run
alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY
```
В современный JavaScript добавлены методы [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) и [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) -- аналоги `String.fromCharCode` и `str.charCodeAt`, корректно работающие с суррогатными парами.
Например, `charCodeAt` считает суррогатную пару двумя разными символами и возвращает код каждой:
```js
//+ run
// как будто в строке два разных символа (на самом деле один)
alert( '𝒳'.charCodeAt(0) + ' ' + '𝒳'.charCodeAt(1) ); // 55349 56499
```
...В то время как `codePointAt` возвращает его Unicode-код суррогатной пары правильно:
```js
//+ run
// один символ с "длинным" (более 2 байт) unicode-кодом
alert( '𝒳'.codePointAt(0) ); // 119987
```
Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода":
```js
//+ run
alert( String.fromCodePoint(119987) ); // 𝒳
alert( String.fromCharCode(119987) ); // 풳
```
Более старый метод `fromCharCode` взял первые два байта от числа `119987` и создал символ из них, а остальные отбросил, поэтому его результат неверен.
### \u{длинный код}
Есть и ещё синтаксическое улучшение для больших Unicode-кодов.
В JavaScript-строках давно можно вставлять символы по Unicode-коду, вот так:
```js
//+ run
alert( "\u2033" ); // ″, символ двойного штриха
```
Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. Вот так -- уже не то:
```js
//+ run
alert( "\u20331" ); // ″1, символ двойного штриха, а затем 1
```
Чтобы вводить более длинные коды символов, добавили запись `\u{NNNNNNNN}`, где `NNNNNNNN` -- максимально восьмизначный (но можно и меньше цифр) код.
Например:
```js
//+ run
alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим кодом
```
### Unicode-нормализация
Во многих языках есть символы, которые получаются как сочетание основного символа и какого-то значка над ним или под ним.
Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Многие, но далеко не все такие сочетания имеют отдельный юникодный код.
Для генерации произвольных сочетаний используются два юникодных символа: основа и значок.
Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то вместе будет `Ṡ`.
Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ.
К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет `Ṩ` с двумя точками сверху и снизу.
В JavaScript-строке:
```js
//+ run
alert("S\u0307\u0323"); // Ṩ
```
Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, чтобы не хранить все возможные сочетания (которых громадное число), а с другой стороны -- возникает проблемка -- возможность представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов.
```js
//+ run
alert("S\u0307\u0323"); // Ṩ
alert("S\u0323\u0307"); // Ṩ
alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false
```
В первой строке сначала верхняя точка, а потом -- нижняя, во второй -- наоборот. По кодам строки не равны друг другу.
С целью разрешить эту ситуацию, существует *юникодная нормализация*, при которой строки приводятся к единому, "нормальному", виду.
В современном JavaScript это делает метод [str.normalize()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/normalize).
```js
//+ run
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
```
Забавно, что в данной конкретной ситуации `normalize()` приведёт последовательность из трёх символов к одному: [\u1e68 (S с двумя точками)](http://www.fileformat.info/info/unicode/char/1e68/index.htm).
```js
//+ run
alert( "S\u0307\u0323".normalize().length ); // 1, нормализовало в один символ
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
```
Это, конечно, не всегда так, просто в данном случае оказалось, что именно такой символ в юникоде уже есть. Если добавить значков, то нормализация уже даст несколько символов.
Если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/).
Впрочем, для большинства практических задач информации, данной выше, должно быть вполне достаточно.
## Полезные методы
Добавлены ряд полезных методов общего назначения:
<ul>
<li>[str.includes(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) -- проверяет, включает ли одна строка в себя другую, возвращает `true/false`.</li>
<li>[str.endsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith) -- возвращает `true`, если строка `str` заканчивается подстрокой `s`.</li>
<li>[str.startsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) -- возвращает `true`, если строка `str` начинается со строки `s`.</li>
<li>[str.repeat(times)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) -- повторяет строку `str` `times` раз.</li>
</ul>
Конечно, всё это можно было сделать при помощи других встроенных методов, но новые методы более удобны.
## Итого
Улучшения:
<ul>
<li>Строки-шаблоны -- для удобного задания строк (многострочных, с переменными), плюс возможность использовать функцию шаблонизации для самостоятельного форматирования.</li>
<li>Юникод -- улучшена работа с суррогатными парами.</li>
<li>Полезные методы для проверок вхождения одной строки в другую.</li>
</ul>

View file

@ -0,0 +1,8 @@
```js
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
```

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,789 @@
# Promise
Promise (обычно их так и называют "промисы") -- предоставляют удобный способ организации асинхронного кода.
В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже.
## Что такое Promise?
Promise -- это специальный объект, который содержит своё состояние. Вначале `pending` ("ожидание"), затем -- одно из: `fulfilled` ("выполнено успешно") или `rejected` ("выполнено с ошибкой").
<img src="promiseInit.png">
На `promise` можно навешивать коллбэки двух типов:
<ul>
<li>`onFulfilled` -- срабатывают, когда `promise` в состоянии "выполнен успешно".</li>
<li>`onRejected` -- срабатывают, когда `promise` в состоянии "выполнен с ошибкой".</li>
</ul>
Способ использования, в общих чертах, такой:
<ol>
<li>Код, которому надо сделать что-то асинхронно, создаёт объект `promise` и возвращает его.</li>
<li>Внешний код, получив `promise`, навешивает на него обработчики.</li>
<li>По завершении процесса асинхронный код переводит `promise` в состояние `fulfilled` (с результатом) или `rejected` (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.</li>
</ol>
Синтаксис создания `Promise`:
```js
var promise = new Promise(function(resolve, reject) {
// Эта функция будет вызвана автоматически
// В ней можно делать любые асинхронные операции,
// А когда они завершаться — нужно вызвать одно из:
// resolve(результат) при успешном выполнении
// reject(ошибка) при ошибке
})
```
Универсальный метод для навешивания обработчиков:
```
promise.then(onFulfilled, onRejected)
```
<ul>
<li>`onFulfilled` -- функция, которая будет вызвана с результатом при `resolve`.</li>
<li>`onRejected` -- функция, которая будет вызвана с ошибкой при `reject`.</li>
</ul>
С его помощью можно назначить как оба обработчика сразу, так и только один:
```js
// только на успешное выполнение
promise.then(onFulfilled)
// только на ошибку
promise.then(null, onRejected)
```
[smart header=".catch"]
Для того, чтобы поставить обработчик только на ошибку, вместо `.then(null, onRejected)` можно написать `.catch(onRejected)` -- это то же самое.
[/smart]
[smart header="Синхронный `throw` -- то же самое, что `reject`"]
Если в функции промиса происходит синхронный `throw` (или иная ошибка), то вызывается `reject`:
```js
//+ run
var p = new Promise((resolve, reject) => {
// то же что reject(new Error("o_O"))
throw new Error("o_O");
})
p.catch(alert); // Error: o_O
```
[/smart]
Посмотрим, как это выглядит вместе, на простом примере.
## Пример с setTimeout
Возьмём `setTimeout` в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом "result":
```js
//+ run
// Создаётся объект promise
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
// переведёт промис в состояние fulfilled с результатом "result"
resolve("result");
}, 1000);
});
// promise.then навешивает обработчики на успешный результат или ошибку
promise
.then(
result => {
// первая функция-обработчик - запустится при вызове resolve
alert("Fulfilled: " + result); // result - аргумент resolve
},
error => {
// вторая функция - запустится при вызове reject
alert("Rejected: " + error); // error - аргумент reject
}
);
```
В результате запуска кода выше -- через 1 секунду выведется "Fulfilled: result".
А если бы вместо `resolve("result")` был вызов `reject("error")`, то вывелось бы "Rejected: error". Впрочем, как правило, если при выполнении возникла проблема, то `reject` вызывают не со строкой, а с объектом ошибки типа `new Error`:
```js
//+ run
// Этот promise завершится с ошибкой через 1 секунду
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
*!*
reject(new Error("время вышло!"));
*/!*
}, 1000);
});
promise
.then(
result => alert("Fulfilled: " + result),
*!*
error => alert("Rejected: " + error.message) // Rejected: время вышло!
*/!*
);
```
Конечно, вместо `setTimeout` мог бы быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс.
[smart header="Только один аргумент"]
Функции `resolve/reject` принимают ровно один аргумент -- результат/ошибку.
Именно он передаётся обработчикам в `.then`, как можно видеть в примерах выше.
[/smart]
## Promise после reject/resolve -- неизменны
Заметим, что после вызова `resolve/reject` промис уже не может "передумать".
Когда промис переходит в состояние "выполнен" -- с результатом (resolve) или ошибкой (reject) -- это навсегда.
Например:
```js
//+ run
var promise = new Promise((resolve, reject) => {
*!*
// через 1 секунду готов результат: result
*/!*
setTimeout(() => resolve("result"), 1000);
*!*
// через 2 секунды — reject с ошибкой, он будет проигнорирован
*/!*
setTimeout(() => reject(new Error("ignored")), 2000);
});
promise
.then(
result => alert("Fulfilled: " + result), // сработает
error => alert("Rejected: " + error) // не сработает
);
```
В результате вызова этого кода сработает только первый обработчик `then`, так как после вызова `resolve` промис уже получил состояние (с результатом), и в дальнейшем его уже ничто не изменит.
Последующие вызовы resolve/reject будут просто проигнороированы.
А так -- наоборот, ошибка будет раньше:
```js
//+ run
var promise = new Promise((resolve, reject) => {
// reject вызван раньше, resolve будет проигнорирован
setTimeout(() => reject(new Error("error")), 1000);
setTimeout(() => resolve("ignored"), 2000);
});
promise
.then(
result => alert("Fulfilled: " + result), // не сработает
error => alert("Rejected: " + error) // сработает
);
```
## Промисификация
*Промисификация* -- это когда берут асинхронный функционал и делают для него обёртку, возвращающую промис.
После промисификации использование функционала зачастую становится гораздо удобнее.
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
Функция `httpGet(url)` будет возвращать промис, который при успешной загрузки данных с `url` будет переходить в `fulfilled` с этими данными, а при ошибке -- в `rejected` с информацией об ошибке:
```js
//+ autorun
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();
});
}
```
Как видно, внутри функции объект `XMLHttpRequest` создаётся и отсылается как обычно, при `onload/onerror` вызываются, соответственно, `resolve` (при статусе 200) или `reject`.
Использование:
```js
//+ run
httpGet("/article/promise/user.json")
.then(
response => alert(`Fulfilled: ${response}`),
error => alert(`Rejected: ${error}`)
);
```
[smart header="Метод `fetch`"]
Заметим, что ряд современных браузеров уже поддерживает [fetch](https://fetch.spec.whatwg.org) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он, конечно, гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
[/smart]
## Цепочки промисов
"Чейнинг" (chaining), то есть возможность строить асинхронные цепочки из промисов -- пожалуй, основная причина, из-за которой существуют и активно используются промисы.
Например, мы хотим по очереди:
<ol>
<li>Загрузить данные посетителя с сервера (асинхронно).</li>
<li>Затем отправить запрос о нём на github (асинхронно).</li>
<li>Когда это будет готово, вывести его github-аватар на экран (асинхронно).</li>
<li>...И сделать код расширяемым, чтобы цепочку можно было легко продолжить.</li>
</ol>
Вот код для этого, использующий функцию `httpGet`, описанную выше:
```js
//+ run
'use strict';
// сделать запрос
httpGet('/article/promise/user.json')
*!*
// 1. Получить данные о пользователе в JSON и передать дальше
*/!*
.then(response => {
console.log(response);
let user = JSON.parse(response);
*!*
return user;
*/!*
})
*!*
// 2. Получить информацию с github
*/!*
.then(user => {
console.log(user);
*!*
return httpGet(`https://api.github.com/users/${user.name}`);
*/!*
})
*!*
// 3. Вывести аватар на 3 секунды (можно с анимацией)
*/!*
.then(githubUser => {
console.log(githubUser);
githubUser = JSON.parse(githubUser);
let img = new Image();
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.appendChild(img);
*!*
setTimeout(() => img.remove(), 3000); // (*)
*/!*
});
```
Самое главное в этом коде -- последовательность вызовов:
```js
httpGet(...)
.then(...)
.then(...)
.then(...)
```
При чейнинге, то есть последовательных вызовах `.then...then..then`, в каждый следующий `then` переходит результат от предыдущего.
**Причём, если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
В коде выше:
<ol>
<li>В первом `then` возвращается объект `user`, он переходит в следующий `then`.</li>
<li>Во втором `then` возвращается промис (результат `loadUser`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.</li>
<li>Третий `then` ничего не возвращает.</li>
</ol>
Схематично его работу можно изобразить так:
<img src="promiseUserFlow.png">
Значком "песочные часы" помечены периоды ожидания. Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки ждёт.
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис.
Это стандартный приём. Если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис, который перейдёт в состояние "выполнен" после `setTimeout`.
Строку `(*)` для этого нужно переписать так:
```js
.then(githubUser => {
...
// вместо setTimeout(() => img.remove(), 3000); (*)
return new Promise((resolve, reject) => {
setTimeout(() => {
img.remove();
// после таймаута — вызов resolve,
// можно без результата, чтобы управление перешло в следующий then
// (или можно передать данные пользователя дальше по цепочке)
resolve();
}, 3000);
});
})
```
Теперь, если к цепочке добавить ещё `then`, то он будет вызван после окончания `setTimeout`.
## Перехват ошибок
Выше мы рассмотрели "идеальный случай" выполнения, когда ошибок нет.
А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?
Да мало ли, где ошибка...
Правило здесь очень простое.
**При возникновении ошибки -- она отправляется в ближайший обработчик `onRejected`.**
Такой обработчик нужно поставить через второй аргумент `.then(..., onRejected)` или, что то же самое, через `.catch(onRejected)`.
Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим `catch` в конец нашей цепочки:
```js
//+ run
'use strict';
*!*
// в httpGet обратимся к несуществующей странице
*/!*
httpGet('/page-not-exists')
.then(response => JSON.parse(response))
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
.then(githubUser => {
githubUser = JSON.parse(githubUser);
let img = new Image();
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.appendChild(img);
return new Promise((resolve, reject) => {
setTimeout(() => {
img.remove();
resolve();
}, 3000);
});
})
*!*
.catch(error => {
alert(error); // Error: Not Found
});
*/!*
```
В примере выше ошибка возникает в первом же `httpGet`, но `catch` с тем же успехом поймал бы ошибку во втором `httpGet` или в `JSON.parse`.
Принцип очень похож на обычный `try..catch`: мы делаем асинхронную цепочку из `.then`, а затем, когда нужно перехватить ошибки, вызываем `.catch(onRejected)`.
[smart header="А что после `catch`?"]
Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
Здесь два варианта развития событий:
<ol>
<li>Если ошибка не критичная, то обработчик возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.</li>
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `.catch(onRejected)`.
</li>
</ol>
Это также похоже на обычный `try..catch` -- в блоке `catch` ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает `throw`. Существенное отличие -- в том, что промисы асинхронные, поэтому при отсутствии внешнего `.catch` ошибка не "вываливается" в консоль и не "убивает" скрипт.
Ведь возможно, что новый обработчик `.catch` будет добавлен в цепочку позже.
[/smart]
## Промисы в деталях
Самым основным источником информации по промисам является, разумеется, [стандарт](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"`.
Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
Исключение из этого правила -- если `resolve` возвращает другой Promise. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`.
Он делает следующее:
<ul>
<li>Если `PromiseState == "pending"`, то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.</li>
<li>Иначе обработчики сразу помещаются в очередь на выполнение.</li>
</ul>
Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно -- после (выполнятся в ближайшее время, через асинхронную очередь).
Например:
```js
//+ run
// Промис выполнится сразу же
var promise = new Promise((resolve, reject) => resolve(1));
// PromiseState = "resolved"
// PromiseResult = 1
// Добавили обработчик к выполненному промису
promise.then(alert); // ...он сработает тут же
```
Разумеется, можно добавлять и много обработчиков на один и тот же промис:
```js
//+ run
// Промис выполнится сразу же
var promise = new Promise((resolve, reject) => resolve(1));
promise.then( function f1(result) {
*!*
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;
}
}
*/!*
})
```
<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`, которому будет передана.
## Вспомогательные методы
В классе `Promise` есть следующие статические методы.
### Promise.all(iterable)
Вызов `Promise.all(iterable)` получает массив (или другой итерируемый объект) промисов и возвращает промис, который завершается, когда все они завершаться, с их результатом.
Например:
```js
//+ run
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
});
```
Заметим, что если какой-то из промисов завершился с ошибкой, то результатом `Promise.all` будет эта ошибка. При этом остальные промисы игнорируются.
Например:
```js
//+ run
Promise.all([
httpGet('/article/promise/user.json'),
httpGet('/article/promise/guest.json'),
httpGet('/article/promise/no-such-page.json') // (нет такой страницы)
]).then(
result => alert("не сработает"),
error => alert("Ошибка: " + error.message) // Ошибка: Not Found
)
```
### Promise.race(iterable)
Как и `Promise.all` получает итерируемый объект с промисами и возвращает новый промис.
Но, в отличие от `Promise.all`, результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
Например:
```js
//+ run
Promise.race([
httpGet('/article/promise/user.json'),
httpGet('/article/promise/guest.json')
]).then(firstResult => {
firstResult = JSON.parse(firstResult);
alert( firstResult.name ); // iliakan или guest, смотря что загрузится раньше
});
```
### Promise.resolve(value)
Вызов `Promise.resolve(value)` создаёт успешно выполнившийся промис с результатом `value`.
Он аналогичен конструкции:
```js
new Promise((resolve) => resolve(value))
```
`Promise.resolve`, когда хотят построить асинхронную цепочку, и начальный результат уже есть.
Например:
```js
//+ run
Promise.resolve(window.location) // начать с этого значения
.then(httpGet) // вызвать для него httpGet
.then(alert) // и вывести результат
```
### Promise.reject(error)
Аналогично `Promise.resolve(value)` создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой `error`.
Например:
```js
//+ run
Promise.reject(new Error("..."))
.catch(alert) // Error: ...
```
Метод `Promise.reject` используется очень редко, гораздо реже чем `resolve`, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
### Итого
<ul>
<li>Промис -- это специальный объект, который хранит своё состояние, текущий результат (если есть) и коллбэки.</li>
<li>При создании `new Promise((resolve, reject) => ...)` автоматически запускается функция-аргумент, которая должна вызвать `resolve(result)` при успешном выполнении и `reject(error)` -- при ошибке.</li>
<li>Аргумент `resolve/reject` (только первый, остальные игнорируются) передаётся обработчикам на этом промисе.</li>
<li>Обработчики назначаются вызовом `.then/catch`.</li>
<li>Для передачи результата от одного обработчика к другому используется чейнинг.</li>
</ul>
В современной JavaScript-разработки промисы в явном виде используются, как ни странно, довольно редко.
Тем не менее, понимать промисы нужно обязательно, так как бывают ситуации, когда без них сложно.
Промисы служат основой для более продвинутых способов написания асинхронного кода, использующих генераторы. Мы рассмотрим их далее в этом разделе.
[head]
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
right: 0;
top: 0;
}
</style>
[/head]

View file

@ -0,0 +1,4 @@
{
"name": "guest",
"isAdmin": false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View file

@ -0,0 +1,4 @@
{
"name": "an-unknown-person-32662",
"isAdmin": false
}

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,4 @@
{
"name": "an-unknown-person-32662",
"isAdmin": false
}