Skip to content

Fix promise-chaining #722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions 1-js/11-async/03-promise-chaining/article.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Ланцюжок промісів

Повернемося до проблеми, згаданої в розділі <info:callbacks>: у нас є послідовність асинхронних задач, які потрібно виконувати одну за одною — наприклад, завантаження скриптів. Як ми можемо це добре закодувати?
Повернемося до проблеми, згаданої в розділі <info:callbacks>: у нас є послідовність асинхронних задач, які потрібно виконувати одну за одною — наприклад, завантаження скриптів. Як ми можемо написати код зручно і зрозуміло?

Проміси надають кілька способів вирішення подібних задач.

Expand Down Expand Up @@ -46,7 +46,7 @@ new Promise(function(resolve, reject) {

Усе це працює тому, що кожний виклик `.then` повертає новий проміс, тому ми можемо викликати наступний `.then` на ньому.

Коли обробник повертає значення, воно стає результатом того промісу, тому наступний `.then` викликається з цим значенням.
Коли обробник повертає значення, воно стає результатом його промісу, тому наступний `.then` викликається з цим значенням.

**Класична помилка новачка: технічно ми також можемо додати багато `.then` до одного промісу. Та це не ланцюжок.**

Expand All @@ -72,7 +72,7 @@ promise.then(function(result) {
});
```

Те, що ми зробили тут -- це додали лише кілька обробників до одного промісу. Вони не передають результат один одному; натомість вони обробляють його самостійно.
Те, що ми зробили тут -- це просто додали кілька обробників до одного промісу. Вони не передають результат один одному ланцюжком. Натомість кожен по своєму обробляє результат одного і того ж проміса.

Ось малюнок (порівняйте його з ланцюжком вище):

Expand All @@ -86,7 +86,7 @@ promise.then(function(result) {

Обробник, використанний в `.then(handler)` може створити й повернути проміс.

У цьому випадку інші обробники чекають, поки він виконається, а потім отримають його результат.
У цьому випадку інші обробники чекають, поки він виконається, а потім отримують його результат.

Наприклад:

Expand Down Expand Up @@ -120,15 +120,15 @@ new Promise(function(resolve, reject) {
});
```

Тут перший `.then` показує `1` і повертає `new Promise(…)` у рядку `(*)`. Через одну секунду він вирішується, а результат (аргумент `resolve`, тут це `result * 2`) передається обробнику другого `.then`. Цей обробник знаходиться в рядку `(**)`, він показує `2` і робить те ж саме.
Тут перший `.then` показує `1` і повертає `new Promise(…)` у рядку `(*)`. Через одну секунду він завершується, а результат (аргумент `resolve`, тут це `result * 2`) передається обробнику другого `.then`. Цей обробник знаходиться в рядку `(**)`, він показує `2` і робить те ж саме.

Отже, результат такий же, як і в попередньому прикладі: 1 -> 2 -> 4, але тепер із затримкою в 1 секунду між викликами `alert`.

Повернення промісів дозволяє нам будувати ланцюжки асинхронних дій.

## Приклад: loadScript

Давайте використовувати цю можливість з промісифікацією `loadScript`, визначеною у [попередньому розділі](info:promise-basics#loadscript), щоб завантажувати скрипти один за одним, у послідовності:
Давайте використовувати цю можливість з промісифікацією `loadScript`, описаною у [попередньому розділі](info:promise-basics#loadscript), щоб завантажувати скрипти один за одним, у послідовності:

```js run
loadScript("/article/promise-chaining/one.js")
Expand Down Expand Up @@ -162,7 +162,7 @@ loadScript("/article/promise-chaining/one.js")
```


Тут кожен виклик `loadScript` повертає проміс, а наступний `.then` запускається, коли він виконується. Потім він ініціює завантаження наступного сценарію. Таким чином, скрипти завантажуються один за одним.
Тут кожен виклик `loadScript` повертає проміс, а наступний `.then` запускається, коли він виконується. Потім він ініціює завантаження наступного. Таким чином, скрипти завантажуються один за одним.

Ми можемо додати більше асинхронних дій до ланцюжка. Зверніть увагу, що код все ще "плоский" — він росте вниз, а не вправо. Немає жодних ознак "піраміди приреченості".

Expand All @@ -183,9 +183,9 @@ loadScript("/article/promise-chaining/one.js").then(script1 => {

Цей код робить те ж саме: завантажує 3 скрипти послідовно. Але він "росте вправо". Тож у нас та ж проблема, що й з колбеками.

Люди, які починають використовувати проміси, іноді не знають про ланцюжок, тому пишуть це так. Як правило, перевага віддається ланцюжкам.
Люди, які починають використовувати проміси, іноді не знають про ланцюжок, тому пишуть це так. Але зазвичай краще писати "ланцюжками".

Іноді нормально писати `.then` відразу, оскільки вкладена функція має доступ до зовнішньої області видимості. У наведеному вище прикладі найбільш вкладений колбек має доступ до всіх змінних `script1`, `script2`, `script3`. Але це швидше виняток, ніж правило.
Іноді справді буває потреба писати `.then` всередині. Для того, щоб вкладена функція мала доступ до зовнішньої області видимості. У наведеному вище прикладі найбільш вкладений колбек має доступ до всіх змінних `script1`, `script2`, `script3`. Але така потреба це швидше виняток, ніж правило.


````smart header="Thenables"
Expand Down Expand Up @@ -216,7 +216,7 @@ new Promise(resolve => resolve(1))
.then(alert); // показує 2 через 1000 мс
```

JavaScript перевіряє об’єкт, повернутий обробником `.then` у рядку `(*)`: якщо він має викликаний метод з ім’ям `then`, тоді він викликає цей метод, та надає власні функції `resolve`, `reject` як аргументи (подібно виконавцю) і чекає, поки один з них не буде викликаний. У наведеному вище прикладі `resolve(2)` викликається через 1 секунду `(**)`. Потім результат передається далі по ланцюжку.
JavaScript перевіряє об’єкт, повернутий обробником `.then` у рядку `(*)`: якщо він має придатний до виклику метод з ім’ям `then`, тоді він викликає цей метод, та передає йому власні функції `resolve`, `reject` як аргументи (подібно виконавцю) і чекає, поки один з них не буде викликаний. У наведеному вище прикладі `resolve(2)` викликається через 1 секунду `(**)`. Потім результат передається далі по ланцюжку.

Ця функція дозволяє нам інтегрувати власні об’єкти з ланцюжками промісів без успадкування від `Promise`.
````
Expand All @@ -232,27 +232,27 @@ JavaScript перевіряє об’єкт, повернутий обробни
let promise = fetch(url);
```

Зазначений код робить мережевий запит до `url` і повертає проміс. Проміс розв’язується за допомогою об’єкта `response`, коли віддалений сервер відповідає заголовками, але *до завантаження повної відповіді*.
Зазначений код робить мережевий запит до `url` і повертає проміс. Цей проміс переходить в стан "fullfilled" і його `value` стає об'єкт `response` (іншими словами, проміс завершується об'єктом `response`) як тільки віддалений сервер присилає заголовки, але ще *до завантаження повної відповіді*.

Щоб прочитати повну відповідь, ми повинні викликати метод `response.text()`: він повертає проміс, який виконується, коли повний текст завантажується з віддаленого сервера, і містить цей текст як результат.
Щоб прочитати повну відповідь, ми повинні викликати метод `response.text()`, він в свою чергу повертає новий проміс. Цей новий проміс завершується лише тоді, коли з віддаленого сервера завантажується увесь текст, не лише заголовки. Після завершення він буде містити весь текст відповіді в якості результату.

Наведений нижче код робить запит до `user.json` і завантажує його текст із сервера:

```js run
fetch('/article/promise-chaining/user.json')
// .then нижче запускається, коли віддалений сервер відповідає
// .then нижче запускається, коли віддалений сервер надіслав заголовки
.then(function(response) {
// response.text() повертає новий проміс, який вирішується з повним текстом відповіді,
// коли він завантажується
// response.text() повертає новий проміс, який завершується повним текстом відповіді,
// після того, як текст повністю завантажиться
return response.text();
})
.then(function(text) {
// ...а ось вміст віддаленого файлу
// ...а ось і повний вміст віддаленого файлу
alert(text); // {"name": "iliakan", "isAdmin": true}
});
```

Об’єкт `response`, повернутий із `fetch`, також включає метод `response.json()`, який зчитує віддалені дані у форматі JSON. У нашому випадку це ще зручніше, тож перейдемо до нього.
Об’єкт `response`, повернутий із `fetch`, також має метод `response.json()`, який обробляє отриманий текст і зберігає його у форматі JSON. У нашому випадку він ще зручніший за метод `response.text()`, тому далі будемо використовувати саме його.

Ми також будемо використовувати стрілкові функції для стислості:

Expand Down Expand Up @@ -287,7 +287,7 @@ fetch('/article/promise-chaining/user.json')
});
```

Код працює; дивіться коментарі щодо деталей. Однак у цьому є потенційна проблема, типова помилка для тих, хто починає використовувати проміси.
Код працює; дивіться коментарі для деталей. Однак у цьому є потенційна проблема, типова помилка для тих, хто починає використовувати проміси.

Подивіться на рядок `(*)`: як ми можемо щось зробити *після* того, як аватар закінчить відображатися і видалиться? Наприклад, ми хотіли б показати форму для редагування цього користувача чи щось інше. Поки що такої можливості немає.

Expand Down Expand Up @@ -321,9 +321,9 @@ fetch('/article/promise-chaining/user.json')

Тобто обробник `.then` у рядку `(*)` тепер повертає `new Promise`, який перейде у стан "виконаний" лише після виклику `resolve(githubUser)` у `setTimeout` `(**)`. Наступний `.then` в ланцюжку буде чекати цього.

Як хороша практика, асинхронна дія завжди повинна повертати проміс. Це дає можливість планувати дії після нього; навіть якщо ми не плануємо розширювати ланцюжок зараз, можливо, це нам знадобиться пізніше.
Гарним тоном буде писати код так, щоб асинхронна дія завжди повертала проміс. Це дає можливість іншим програмістам після вас планувати наступні дії, які зможуть почекати завершення попередньої; навіть якщо ми не плануємо розширювати ланцюжок зараз, можливо, це нам знадобиться пізніше.

Нарешті, ми можемо розділити код на функції, що можуть бути перевикористані:
Ну а зараз ми можемо розділити код на функції, що можуть бути перевикористані:

```js run
function loadJson(url) {
Expand Down Expand Up @@ -359,7 +359,7 @@ loadJson('/article/promise-chaining/user.json')

## Підсумки

Якщо обробник `.then` (або `catch/finally`, не має різниці) повертає проміс, решта ланцюжка чекає, доки він виконається. Коли це відбувається, його результат (або помилка) передається далі.
Якщо обробник `.then` (або `catch/finally`, однаково) повертає проміс, решта ланцюжка чекає, доки він виконається. Коли це відбувається, його результат (або помилка) передається далі.

Ось повна картина:

Expand Down