?

Log in

No account? Create an account

[icon] Цепочки обработчиков в JS Promise - Давид Мзареулян
View:Свежие записи.
View:Архив.
View:Друзья.
View:Личная информация.
View:Website (Мои фотографии).
View:Иероглиф. hiero.ru/David. RSS2LJ. Здешние теги.

Tags:,
Security:
Subject:Цепочки обработчиков в JS Promise
Time:01:55 pm

Promise — это относительно новая фича JavaScript-а, позволяющая выполнять отложенные действия — то, что раньше решалось коллбэками. Хорошее введение в эту технологию есть на сайте HTML5Rocks, не буду его пересказывать.

У Promise есть два (интересующих нас сейчас) метода — .then() и .catch(), в которые передаются функции-обработчики для состояний «выполнено» (fulfilled) и «отказ» (rejected) соответственно. Эти методы можно объединять в цепочки: prom.then().then().catch().then().catch().catch().…. Вопрос: в каком порядке вызываются эти обработчики, что они получают, и от чего всё это вообще зависит?

Для начала посмотрим на конструкцию prom.then().then() (здесь prom — это некоторый Promise). Что такое тут prom.then()? Очевидно, это Promise, раз у него есть методы .then() и .catch(). Каково значение этого Promise? «Естественным» может показаться предположение, что это тот же самый prom передаётся по цепочке, но на самом деле это не так (и хорошо, что не так).

Каждый обработчик .then() и .catch() возвращает какое-то значение. Даже если функция не возвращает значение явно, то результатом её всё равно является значение undefined. Далее действуют правила:

  • Если функция возвращает Promise, то именно он и становится новым Promise-ом в цепочке;
  • Если функция возвращает любое другое значение, то оно оборачивается в Promise.resolve(…) и становится Promise-ом в выполненном состоянии;
  • Наконец, если в функции произошло исключение, то значение этого исключения оборачивается в Promise.reject(…) и становится Promise-ом в состоянии отказа.

Самое полезное применение этих правил — возможность обработки данных в цепочке. Например, так:

get('data.json')  
    .then(function(response) {
        // response — текстовые данные
        return JSON.parse(response);
    })
    .then(function(response) {
        // а тут уже response — объект, полученный в предыдущем обработчике
        console.log("Yey JSON!", response);
    });

Но вернёмся к произвольным цепочкам. У нас есть Promise, в зависимости от его состояния, он вызывает .then() или .catch(), ближайшие к нему по цепочке. Вызванный обработчик возвращает новый Promise, который снова вызывает .then() или .catch(), ближайшие по цепочке. И так далее.

Приведём пример (писать буду в ES6-синтаксисе, он проще):

Promise.reject("A")  
    .then(x =>  { console.log("THEN1", x);  })
    .catch(x => { console.log("CATCH1", x); })
    .then(x =>  { console.log("THEN2", x);  })
    .catch(x => { console.log("CATCH2", x); });

Что мы получим в консоли? Вот что:

CATCH1 A
THEN2 undefined

Почему так? У нас есть Promise в состоянии «отказ», значит, он вызовет ближайший обработчик .catch() в цепочке, это CATCH1. Этот обработчик не возвращает ничего (т. е. возвращает undefined), значит, на выходе из него мы получаем выполненный Promise со значением undefined. Выполненый Promise вызывает ближайший к нему .then(), это THEN2.

Ещё пример:

Promise.reject("A")  
    .catch(x => {
        console.log("CATCH1", x);
        return Promise.reject("B");
    })
    .then(x =>  { console.log("THEN1", x);  })
    .catch(x => { console.log("CATCH2", x); })
    .then(x =>  { console.log("THEN2", x);  });

Результат (посчитайте сами, почему именно так):

CATCH1 A
CATCH2 B
THEN2 undefined
Тут, кстати, имеются некоторые подводные грабли. Пусть у нас цепочка prom.then().catch() и предполагается, что .catch() ловит отказ в prom. Если prom выполняется успешно, то вызывается .then(), но если в его обработчике случится какая-то ошибка или исключение, то он вернёт новый Promise в состоянии отказа, что вызовет исполнение .catch(), который, скорее всего, сработает неправильно т. к. ожидает совсем других данных.
Поэтому чтобы гарантировать, что и .then()- и .catch()-обработчики будут иметь дело только с заданным Promise, надо использовать не цепочку вызовов, а вызов .then() с двумя аргументами. Вот так: prom.then(onResolve, onReject). Тогда что бы ни случилось в onResolve, onReject вызван не будет.

Этот пост в блоге POST /blog (13:08 30.08.2015)

Комментарии: написать Previous Entry Поделиться Next Entry

[icon] Цепочки обработчиков в JS Promise - Давид Мзареулян
View:Свежие записи.
View:Архив.
View:Друзья.
View:Личная информация.
View:Website (Мои фотографии).
View:Иероглиф. hiero.ru/David. RSS2LJ. Здешние теги.