diff --git a/1-js/10-es-modern/1-es-modern-usage/article.md b/1-js/10-es-modern/1-es-modern-usage/article.md index 68cb6795..82c8896e 100644 --- a/1-js/10-es-modern/1-es-modern-usage/article.md +++ b/1-js/10-es-modern/1-es-modern-usage/article.md @@ -5,11 +5,11 @@ [/smart] -[Стандарт ES-2015](http://www.ecma-international.org/publications/standards/Ecma-262.htm) был принят в июне 2015. Пока что большинство браузеров реализуют его частично, текущее состояние реализации различных возможностей можно посмотреть здесь: [](https://kangax.github.io/compat-table/es6/), поэтому в этом учебнике ему выделена отдельная секция. +[Стандарт ES-2015](http://www.ecma-international.org/publications/standards/Ecma-262.htm) был принят в июне 2015. Пока что большинство браузеров реализуют его частично, текущее состояние реализации различных возможностей можно посмотреть здесь: [](https://kangax.github.io/compat-table/es6/). -Когда стандарт будет поддерживаться почти целиком везде, то весь учебник будет обновлён в соответствии с ним. +Когда стандарт будет более-менее поддерживаться во всех браузерах, то весь учебник будет обновлён в соответствии с ним. Пока же, как центральное место для "сбора" современных фич JavaScript, создан этот раздел. -Пока же, чтобы писать код на ES-2015, есть следующие варианты. +Чобы писать код на ES-2015 прямо сейчас, есть следующие варианты. ## Конкретный движок JS @@ -62,11 +62,17 @@ # Примеры на этом сайте -На этом сайте запускаемые примеры гарантированно работают в [последнем Chrome](https://www.google.com/chrome/browser/canary.html), браузерный Babel не используется, при желании вы можете запустить их в любом браузере вместе с Babel, как показано выше. - -Ещё раз заметим, что самая актуальная ситуация по поддержке современного стандарта браузерами отражена вот тут: [](https://kangax.github.io/compat-table/es6/). - +Запускаемые примеры с ES-2015 будут работать только если ваш браузер поддерживает соответствующую возможность стандарта. +Рекомендуется [Chrome Canary](https://www.google.com/chrome/browser/canary.html), Edge или [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/channel/#developer). + +Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код. + +На практике для кросс-браузерности всё равно используют Babel. + +Ещё раз заметим, что самая актуальная ситуация по поддержке современного стандарта браузерами и транспайлерами отражена на странице [](https://kangax.github.io/compat-table/es6/). + +Итак, поехали! diff --git a/1-js/10-es-modern/2-let-const/article.md b/1-js/10-es-modern/2-let-const/article.md index 7554fca1..2f889f40 100644 --- a/1-js/10-es-modern/2-let-const/article.md +++ b/1-js/10-es-modern/2-let-const/article.md @@ -61,7 +61,7 @@ alert(apples); // 5 (снаружи блока значение не измен Заметим, что если объявление `apples` в строке `(*)` закомментировать, то в последнем `alert` будет ошибка: переменная неопределена. Это потому что переменная `let` всегда видна именно в том блоке, где объявлена и не более. -
  • **Переменная `let` не видна до объявления.** +
  • **Переменная `let` видна только после объявления.** Как мы помним, переменные `var` существуют и до объявления. Они равны `undefined`: @@ -87,10 +87,50 @@ alert(a); // ошибка, нет такой переменной let a = 5; ``` + +Заметим также, что переменные `let` нельзя повторно объявлять. То есть, такой код выведет ошибку: + +```js +//+ run +'use strict'; + +let x; +let x; // ошибка: переменная x уже объявлена +``` + +Это -- хоть и выглядит ограничением по сравнению с `var`, но на самом деле проблем не создаёт, так как область видимости ограничена блоком. + +Например, два таких цикла совсем не конфликтуют: +```js +//+ run +'use strict'; + +for(let i = 0; i<10; i++) { /* … */ } +for(let i = 0; i<10; i++) { /* … */ } + +alert( i ); // ошибка, переменная не определена +``` + +При объявлении внутри цикла переменная `i` будет видна только в блоке цикла. Она не видна снаружи, поэтому будет ошибка в последнем `alert`. + +
  • **При использовании в цикле, для каждой итерации создаётся своя переменная.** -Объявление `let` позволяет легко решить классическую проблему с замыканиями, описанную в задаче [](/task/make-army). +Переменная `var` -- одна на все итерации цикла (и видна после цикла): + +```js +//+ run +for(var i=0; i<10; i++) { /* … */ } + +alert(i); // 10 +``` + +С переменной `let` -- всё по-другому. Добавляется ещё одна область видимости: блок цикла. + +Каждому блоку цикла соответствует своя, независимая, переменная `let`. Если внутри цикла объявляются функции, то в замыкании каждой будет та переменная, которая была при итерации. + +Это позволяет легко решить классическую проблему с замыканиями, описанную в задаче [](/task/make-army). ```js //+ run @@ -117,13 +157,13 @@ army[5](); // 5 Если бы объявление было `var i`, то была бы одна переменная `i` на всю функцию, и вызовы в последних строках выводили бы `10` (подробнее -- см. задачу [](/task/make-army)). -Объявление `let i` создаёт для каждого повторения блока в цикле свою переменную. +А выше объявление `let i` создаёт для каждого повторения блока в цикле свою переменную, которую функция и получает из замыкания в последних строках.
  • -# const +## const -По области видимости -- это то же самое, что `let`, но константа, нельзя менять: +Объявление `const` задаёт константу, то есть переменную, которую нельзя менять: ```js //+ run @@ -133,5 +173,18 @@ const apple = 5; apple = 10; // ошибка ``` +В остальном объявление `const` полностью аналогично `let`. + +## Итого +Переменные `let`: + + + +Переменная `const` -- это константа, в остальном -- как `let`. + diff --git a/1-js/10-es-modern/3-es-function/article.md b/1-js/10-es-modern/3-es-function/article.md deleted file mode 100644 index e69de29b..00000000 diff --git a/1-js/10-es-modern/4-es-string/article.md b/1-js/10-es-modern/4-es-string/article.md deleted file mode 100644 index c3543391..00000000 --- a/1-js/10-es-modern/4-es-string/article.md +++ /dev/null @@ -1,95 +0,0 @@ - -# Строки и регэкспы - -Есть ряд улучшений и новых методов для строк и регулярных выражений. - -Начнём с, пожалуй, самого важного. - -## Строки-шаблоны - -Добавлен новый вид кавычек для строк: -```js -let str = `обратные кавычки`; -``` - -Основные отличия от `"…"` и `'…'`: - - - - - - -Основные улучшения для строк -- улучшенная поддержка юникода. Кроме того, добавлены некоторые новые методы. - -## Улучшена поддержка юникода - -[smart header="А ваши сайты читают китайцы?"] -Это улучшение имеет значение только если мы поддерживаем китайский и другие языки, которые выходят за границы стандартного 2-байтового диапазона. -[/smart] - -Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта. - -Под всевозможные символы всех языков мира двух байт не хватает. - -Поэтому, к примеру, китайские иероглифы представляются двумя юникодными символами, например: - -```js -//+ run -alert( "我".length ); // 2 -``` - -Такое представление, когда одному символу языка соответствует два юникодных символа, называют "суррогатные пары". - -Ю \ No newline at end of file diff --git a/1-js/10-es-modern/5-promise/1-promise-settimeout/solution.md b/1-js/10-es-modern/5-promise/1-promise-settimeout/solution.md deleted file mode 100644 index 791fe90e..00000000 --- a/1-js/10-es-modern/5-promise/1-promise-settimeout/solution.md +++ /dev/null @@ -1,8 +0,0 @@ - -```js -function delay(ms) { - return new Promise((resolve, reject) => { - setTimeout(resolve, ms); - }); -} -``` \ No newline at end of file diff --git a/1-js/10-es-modern/5-promise/1-promise-settimeout/task.md b/1-js/10-es-modern/5-promise/1-promise-settimeout/task.md deleted file mode 100644 index 0dd9edc4..00000000 --- a/1-js/10-es-modern/5-promise/1-promise-settimeout/task.md +++ /dev/null @@ -1,27 +0,0 @@ - -# Промисифицировать 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); -``` diff --git a/1-js/10-es-modern/5-promise/anon.png b/1-js/10-es-modern/5-promise/anon.png deleted file mode 100644 index a1675d2f..00000000 Binary files a/1-js/10-es-modern/5-promise/anon.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/article.md b/1-js/10-es-modern/5-promise/article.md deleted file mode 100644 index f9b75db9..00000000 --- a/1-js/10-es-modern/5-promise/article.md +++ /dev/null @@ -1,789 +0,0 @@ -# Promise - -Promise (обычно их так и называют "промисы") -- предоставляют удобный способ организации асинхронного кода. - -В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже. - -## Что такое Promise? - -Promise -- это специальный объект, который содержит своё состояние. Вначале `pending` ("ожидание"), затем -- одно из: `fulfilled` ("выполнено успешно") или `rejected` ("выполнено с ошибкой"). - - - -На `promise` можно навешивать коллбэки двух типов: - - - -Способ использования, в общих чертах, такой: -
      -
    1. Код, которому надо сделать что-то асинхронно, создаёт объект `promise` и возвращает его.
    2. -
    3. Внешний код, получив `promise`, навешивает на него обработчики.
    4. -
    5. По завершении процесса асинхронный код переводит `promise` в состояние `fulfilled` (с результатом) или `rejected` (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.
    6. -
    - -Синтаксис создания `Promise`: - -```js -var promise = new Promise(function(resolve, reject) { - // Эта функция будет вызвана автоматически - - // В ней можно делать любые асинхронные операции, - // А когда они завершаться — нужно вызвать одно из: - // resolve(результат) при успешном выполнении - // reject(ошибка) при ошибке -}) -``` - -Универсальный метод для навешивания обработчиков: - -``` -promise.then(onFulfilled, onRejected) -``` - - - -С его помощью можно назначить как оба обработчика сразу, так и только один: - -```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), то есть возможность строить асинхронные цепочки из промисов -- пожалуй, основная причина, из-за которой существуют и активно используются промисы. - -Например, мы хотим по очереди: -
      -
    1. Загрузить данные посетителя с сервера (асинхронно).
    2. -
    3. Затем отправить запрос о нём на github (асинхронно).
    4. -
    5. Когда это будет готово, вывести его github-аватар на экран (асинхронно).
    6. -
    7. ...И сделать код расширяемым, чтобы цепочку можно было легко продолжить.
    8. -
    - -Вот код для этого, использующий функцию `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` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.** - -В коде выше: - -
      -
    1. В первом `then` возвращается объект `user`, он переходит в следующий `then`.
    2. -
    3. Во втором `then` возвращается промис (результат `loadUser`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.
    4. -
    5. Третий `then` ничего не возвращает.
    6. -
    - -Схематично его работу можно изобразить так: - - - -Значком "песочные часы" помечены периоды ожидания. Если `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)` получает ошибку и должен обработать её. - -Здесь два варианта развития событий: -
      -
    1. Если ошибка не критичная, то обработчик возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.
    2. -
    3. Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в ближайший `.catch(onRejected)`. -
    4. -
    - -Это также похоже на обычный `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)` при создании есть четыре внутренних свойства: - - - - - -Когда функция-executor вызывает reject или resolve, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`. - -Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`. - -Исключение из этого правила -- если `resolve` возвращает другой Promise. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним. - -Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`. - -Он делает следующее: - - -Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно -- после (выполнятся в ближайшее время, через асинхронную очередь). - -Например: - -```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` после этого: - - - -На этой иллюстрации можно увидеть, что `.then` если один из обработчиков не указан, добавляет его "от себя", следующим образом: - - -Это, по сути дела, формальность, но без неё некоторые особенности поведения промисов могут "не сойтись" в общую логику, поэтому мы упоминаем о ней здесь. - -Обратим внимание, в этом примере намеренно *не используется чейнинг*. То есть, обработчики добавляются именно на один и тот же промис. - -Поэтому оба `alert` выдадут одно значение `1`. Алгоритм такой -- все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется. - -**Для того, чтобы результат обработчика передать следующей функции, `.then` создаёт новый промис и возвращает его.** - -В примере выше создаётся два таких промиса, каждый из которых даёт свою ветку выполнения: - - - -Изначально эти новые промисы -- пустые. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу: - - - - - -Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара. - -Первый промис и обработка его результата: - -```js -httpGet('/article/promise/user.json') - .then(JSON.parse) -``` - - - - -Если промис завершился через `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}`)) -*/!* -``` - -Цепочка "выросла вниз": - - - -Функция `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; - } - } -*/!* - }) -``` - - - -Наконец-то хоть какая-то обработка ошибок! - -Обработчик `avatarError` перехватит ошибки, которые были ранее. Функция `httpGet` при генерации ошибки записывает её HTTP-код в свойство `error.code`, так что мы легко можем понять -- что это: - - - -Итого, после добавления оставшейся части цепочки, картина получается следующей: - -```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 - }); -``` - - - -В конце срабатывает общий обработчик `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`, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения. - -### Итого - - - -В современной JavaScript-разработки промисы в явном виде используются, как ни странно, довольно редко. - -Тем не менее, понимать промисы нужно обязательно, так как бывают ситуации, когда без них сложно. - -Промисы служат основой для более продвинутых способов написания асинхронного кода, использующих генераторы. Мы рассмотрим их далее в этом разделе. - - -[head] - -[/head] diff --git a/1-js/10-es-modern/5-promise/guest.json b/1-js/10-es-modern/5-promise/guest.json deleted file mode 100644 index c32ac845..00000000 --- a/1-js/10-es-modern/5-promise/guest.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "guest", - "isAdmin": false -} diff --git a/1-js/10-es-modern/5-promise/promiseEcma.png b/1-js/10-es-modern/5-promise/promiseEcma.png deleted file mode 100644 index f27e0e0b..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseEcma.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseEcma@2x.png b/1-js/10-es-modern/5-promise/promiseEcma@2x.png deleted file mode 100644 index 6fa3bd76..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseEcma@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseHandlerVariants.png b/1-js/10-es-modern/5-promise/promiseHandlerVariants.png deleted file mode 100644 index c2c4a734..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseHandlerVariants.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseHandlerVariants@2x.png b/1-js/10-es-modern/5-promise/promiseHandlerVariants@2x.png deleted file mode 100644 index 5f1f4da6..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseHandlerVariants@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseInit.png b/1-js/10-es-modern/5-promise/promiseInit.png deleted file mode 100644 index dc2e324f..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseInit.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseInit@2x.png b/1-js/10-es-modern/5-promise/promiseInit@2x.png deleted file mode 100644 index 957f91f7..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseInit@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1.png deleted file mode 100644 index 3f84d6fd..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1@2x.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1@2x.png deleted file mode 100644 index 6602a3fb..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-1@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2.png deleted file mode 100644 index 9d2c01cb..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2@2x.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2@2x.png deleted file mode 100644 index c8ae285e..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-2@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3.png deleted file mode 100644 index 27a61c64..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3@2x.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3@2x.png deleted file mode 100644 index 3954f250..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-3@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4.png deleted file mode 100644 index a59a1a44..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4@2x.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4@2x.png deleted file mode 100644 index 6c196a3c..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain-4@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain.png deleted file mode 100644 index ea162ad1..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain@2x.png b/1-js/10-es-modern/5-promise/promiseLoadAvatarChain@2x.png deleted file mode 100644 index 1e5f23bb..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseLoadAvatarChain@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseTwo.png b/1-js/10-es-modern/5-promise/promiseTwo.png deleted file mode 100644 index 9ef03e37..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseTwo.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseTwo@2x.png b/1-js/10-es-modern/5-promise/promiseTwo@2x.png deleted file mode 100644 index 1e9ea11d..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseTwo@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseTwoThen.png b/1-js/10-es-modern/5-promise/promiseTwoThen.png deleted file mode 100644 index 25922cc0..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseTwoThen.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseTwoThen@2x.png b/1-js/10-es-modern/5-promise/promiseTwoThen@2x.png deleted file mode 100644 index b2130043..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseTwoThen@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseUserFlow.png b/1-js/10-es-modern/5-promise/promiseUserFlow.png deleted file mode 100644 index 19b2a7cf..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseUserFlow.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/promiseUserFlow@2x.png b/1-js/10-es-modern/5-promise/promiseUserFlow@2x.png deleted file mode 100644 index fff1d353..00000000 Binary files a/1-js/10-es-modern/5-promise/promiseUserFlow@2x.png and /dev/null differ diff --git a/1-js/10-es-modern/5-promise/user-no-guthub.json b/1-js/10-es-modern/5-promise/user-no-guthub.json deleted file mode 100644 index 9e23f32b..00000000 --- a/1-js/10-es-modern/5-promise/user-no-guthub.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "an-unknown-person-32662", - "isAdmin": false -} diff --git a/1-js/10-es-modern/5-promise/user.json b/1-js/10-es-modern/5-promise/user.json deleted file mode 100644 index 32f89971..00000000 --- a/1-js/10-es-modern/5-promise/user.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "iliakan", - "isAdmin": true -} diff --git a/1-js/10-es-modern/5-promise/userNoGithub.json b/1-js/10-es-modern/5-promise/userNoGithub.json deleted file mode 100644 index 9e23f32b..00000000 --- a/1-js/10-es-modern/5-promise/userNoGithub.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "an-unknown-person-32662", - "isAdmin": false -}