es6
227
1-js/10-es-modern/3-destructuring/article.md
Normal 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>
|
||||
|
||||
Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
414
1-js/10-es-modern/4-es-function/article.md
Normal 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
320
1-js/10-es-modern/5-es-string/article.md
Normal 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>
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
```js
|
||||
function delay(ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
```
|
27
1-js/10-es-modern/6-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/6-promise/anon.png
Normal file
After Width: | Height: | Size: 30 KiB |
789
1-js/10-es-modern/6-promise/article.md
Normal 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]
|
4
1-js/10-es-modern/6-promise/guest.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "guest",
|
||||
"isAdmin": false
|
||||
}
|
BIN
1-js/10-es-modern/6-promise/promiseEcma.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
1-js/10-es-modern/6-promise/promiseEcma@2x.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
1-js/10-es-modern/6-promise/promiseHandlerVariants.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
1-js/10-es-modern/6-promise/promiseHandlerVariants@2x.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
1-js/10-es-modern/6-promise/promiseInit.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
1-js/10-es-modern/6-promise/promiseInit@2x.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-1.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-1@2x.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-2.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-2@2x.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-3.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-3@2x.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-4.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain-4@2x.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
1-js/10-es-modern/6-promise/promiseLoadAvatarChain@2x.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
1-js/10-es-modern/6-promise/promiseTwo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
1-js/10-es-modern/6-promise/promiseTwo@2x.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
1-js/10-es-modern/6-promise/promiseTwoThen.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
1-js/10-es-modern/6-promise/promiseTwoThen@2x.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
1-js/10-es-modern/6-promise/promiseUserFlow.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
1-js/10-es-modern/6-promise/promiseUserFlow@2x.png
Normal file
After Width: | Height: | Size: 76 KiB |
4
1-js/10-es-modern/6-promise/user-no-guthub.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "an-unknown-person-32662",
|
||||
"isAdmin": false
|
||||
}
|
4
1-js/10-es-modern/6-promise/user.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "iliakan",
|
||||
"isAdmin": true
|
||||
}
|
4
1-js/10-es-modern/6-promise/userNoGithub.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "an-unknown-person-32662",
|
||||
"isAdmin": false
|
||||
}
|