Изменение нескольких элементов и вызов цепочки методов. Attribute span window - Цепочка вызовов.bind () в JavaScript. Неожиданный результат

Одной из особенностей, которые делают jQuery таким четким и выразительным, является то, что вызов метода для jQuery объекта, как правило, меняет все элементы, которые содержатся в данном объекте. Я сказал "как правило", потому что некоторые методы совершают операции, выполнение которых для множества элементов не имеет смысла; вы это увидите в следующих главах. В показано, насколько сильно jQuery облегчает жизнь по сравнению с базовым DOM API.

Листинг 5-21: Работа с несколькими элементами ... ; var labelElems = document.getElementsByTagName("label"); for (var i = 0; i < labelElems.length; i++) { labelElems[i].style.color = "blue"; } }); ...

В этом примере я выбираю в документе все элементы label и меняю значение свойства color на blue . В jQuery я делаю это в одном выражении, а при использовании DOM API на это потребуется больше усилий. Также мне кажется, что смысл выражения jQuery очевиден, но это мое личное мнение.

Другой очень хорошей особенностью объекта jQuery является то, что он реализует текучий интерфейс . Это обозначает, что где бы вы ни вызвали метод, который меняет содержание объекта, результатом этого метода будет другой jQuery объект. Это может показаться простым, но это позволяет использовать цепочку методов, как показано в .

Листинг 5-22: Применение цепочки методов для jQuery объекта ... $(document).ready(function () { $("label").css("color", "blue").css("font-size", ".75em"); var labelElems = document.getElementsByTagName("label"); for (var i = 0; i < labelElems.length; i++) { labelElems[i].style.color = "blue"; labelElems[i].style.fontSize = ".75em"; } }); ...

В этом примере я создал jQuery объект, используя $ функцию, вызвал метод css и установил значение для свойства color , а затем снова вызвал метод css , на этот раз чтобы установить значение для свойства font-size . Также я продемонстрировал эквивалентное решение, используя DOM API. Вы видите, что тут не требуется намного больше работы, чтобы получить тот же результат, потому что есть цикл for , который перечисляет выбранные элементы.

Вы получите реальную выгоду тогда, когда будете использовать цепочку методов, которые вносят более существенные изменения в коллекцию элементов, содержащихся в jQuery объекте. В показан пример.

Листинг 5-23: Более сложный пример использования цепочки методов $(document).ready(function () { $("label").css("color", "blue").add("input") .filter("").css("font-size", ".75em"); var elems = document.getElementsByTagName("label"); for (var i = 0; i < elems.length; i++) { elems[i].style.color = "blue"; if (elems[i].getAttribute("for") != "snowdrop") { elems[i].style.fontSize = ".75em"; } } elems = document.getElementsByTagName("input"); for (var i = 0; i < elems.length; i++) { if (elems[i].getAttribute("name") != "rose") { elems[i].style.fontSize = ".75em"; } } });

В этом примере хорошо продемонстрирована гибкость jQuery. Давайте рассмотрим каждый метод по отдельности, чтобы понять, как это работает. Начнем с этого:

$("label").css("color", "blue")

Это хорошее и простое начало. Я выбрал в документе все элементы label и для них всех CSS свойству color установил значение blue . Следующий шаг:

$("label").css("color", "blue").add("input")

Метод add добавляет элементы, которые соответствуют указанному селектору в объект jQuery. В данном случае я выбрал все элементы input , значение атрибута name которых не является rose . Эти элементы объединяются с предыдущими выбранными элементами, и у меня есть комплекс из элементов label и input . О методе add подробнее будет рассказано в главе 6. Вот следующее дополнение:

$("label").css("color", "blue").add("input").filter("")

Метод filter убирает все элементы из jQuery объекта, которые не соответствуют указанному условию. Более подробно об этом методе я расскажу в главе 6, а на данный момент достаточно знать, что это позволяет мне удалить любой элемент из jQuery объекта, значением атрибута for которого является snowdrop .

$("label").css("color", "blue").add("input") .filter("").css("font-size", ".75em") ;

И завершающим шагом является вызов метода css снова, на этот раз для указания значения.75em свойству font-size . И вот конечный результат этой работы:

  • Для всех элементов label для CSS свойства color установлено значение blue .
  • Для всех элементов label кроме одного, значение атрибута for которого равно snowdrop , присвоено значение.75em CSS свойству font-size .
  • Для всех элементов input , у которых значение атрибута name не равно rose , присвоено значение.75em CSS свойству font-size .
  • Намного сложнее получить такой же результат при использовании DOM API, и я столкнулся с некоторыми сложностями, пока писал этот скрипт. Например, я думал, что могу использовать метод document.querySelectorAll , описанный во второй главе , чтобы выбрать input элементы, используя селектор input , но выяснилось, что атрибутивный фильтр такого рода не работает с этим методом. Потом я пытался избежать дублирования вызова метода для указания значения для font-size , объединив результаты двух getElementsByTagName , но этот опыт оказался довольно болезненным. Я не хочу мучить вас подробностями, тем более если у вас есть устойчивый интерес к jQuery и прочтению данной книги. Но хочу сказать, что jQuery обеспечивает тот уровень гибкости и выразительности, которого невозможно достичь, используя базовый DOM API.

    Отличный ответ torazaburo дал мне идею. Было бы возможно, чтобы функция, подобная связыванию, вместо того, чтобы выпекать приемник (этот) в вызове внутри замыкания, чтобы поместить его как свойство в объект функции, а затем использовать его при вызове. Это позволит повторной попытке обновить свойство до того, как будет выполнен вызов, эффективно предоставив ожидаемые результаты повторной обработки.

    Например,

    function original_fn() { document.writeln(JSON.stringify(this)); } Function.prototype.rebind = function(obj) { var fn = this; var bound = function func() { fn.call(func.receiver, arguments); }; bound.receiver = obj; bound.rebind = function(obj) { this.receiver = obj; return this; }; return bound; } var bound_fn = original_fn.rebind({foo: "bar"}); bound_fn(); var rebound_fn = bound_fn.rebind({fred: "barney"}); rebound_fn();

    Или вывод из node.js выглядит следующим образом.

    { foo: "bar" } { fred: "barney" }

    Обратите внимание, что первый вызов для rebind вызывает тот, который был добавлен в Function.prototype поскольку он вызывается в обычной функции original_fn , но второй вызов вызывает rebind которая была добавлена ​​как свойство связанной функции (и любой последующий вызовы вызовут это тоже). Эта rebind просто обновляет receiver и возвращает тот же объект функции.

    Можно было получить доступ к свойству receiver в связанной функции, сделав его именованным функциональным выражением .

    2018-12-04T00:00Z

    Заманчиво думать о bind как о какой-то модификации функции, чтобы использовать это новое. В этой (неправильной) интерпретации люди думают о bind добавляя какой-то волшебный флаг в функцию, говорящую ему использовать другое в следующий раз, когда он вызывается. Если это так, тогда должно быть возможно «переопределить» и изменить волшебный флаг. И тогда спросите, в чем причина произвольного ограничения возможности сделать это?

    Но на самом деле это не так, как это работает. bind создает и возвращает новую функцию, которая при вызове вызывает первую функцию с конкретным значением. Поведение этой вновь созданной функции, чтобы использовать указанное для вызова исходной функции, записывается при создании функции. Он не может быть изменен больше, чем внутренние функции любой другой функции, возвращаемой функцией, могут быть изменены после факта.

    Это может помочь взглянуть на реальную простую реализацию bind:

    // NOT the real bind; just an example Function.prototype.bind = function(ctxt) { var fn = this; return function bound_fn() { return fn.apply(ctxt, arguments); }; } my_bound_fn = original_fn.bind(obj);

    Как вы можете видеть, нигде в bound_fn функция, возвращаемая из bind , ссылается на this с которой была вызвана связанная функция. Он игнорируется, так что

    My_bound_fn.call(999, arg) // 999 is ignored

    Obj = { fn: function () { console.log(this); } }; obj.fn = obj.fn.bind(other_obj); obj.fn(); // outputs other_obj; obj is ignored

    Поэтому я могу связать функцию, возвращенную из bind «снова», но это не отталкивает исходную функцию; это просто привязка внешней функции, которая не влияет на внутреннюю функцию, поскольку она уже настроена для вызова базовой функции с контекстом (this значение), переданным для bind . Я могу связывать снова и снова, но все, что я делаю, это создание более внешних функций, которые могут быть связаны чем-то, но в конечном итоге вызывают самую внутреннюю функцию, возвращаемую из первого bind .

    Поэтому несколько неверно утверждать, что bind «нельзя переопределить».

    Если я хочу «восстановить» функцию, тогда я могу просто создать новое связывание с исходной функцией. Поэтому, если я связал его один раз:

    Function orig() { } my_bound_fn = orig.bind(my_obj);

    а затем я хочу, чтобы моя оригинальная функция была вызвана с некоторыми другими, тогда я не перепровергаю связанную функцию.

    Когда jQuery, библиотека JavaScript, увидела свет в январе 2006, все подумали: «очередная красивая игрушка». Выбор CSS-селекторов в качестве базиса было, конечно, изящной идеей, но использование цепочек преобразований выглядело немного замысловато, и сама библиотека, по-видимому, не покрывала всех возможных случаев. jQuery расценивали тогда только как временное и проходящее решение.

    Только несколько месяцев спустя стало понятно, что jQuery является просто произведением инженерного искусства. Она умело покрывает достаточно широкий диапазон повседневных функций и предоставляет при этом удобный API для расширений, с помощью которых можно добавить любую другую функциональность. Абстрактность в ней заложена на уровне ядра - речь идет о выборе элементов объектной модели документов (DOM) - и она извлекает из него максимум пользы. И что важнее всего, использование этой библиотеки подразумевает следование хорошему стилю в программировании и хорошо сочетается с другими частями JavaScript-кода.

    Большинство современных обзоров jQuery делают упор на дизайнеров и неопытных разработчиков. Я попытаюсь объяснить, почему она также нужна и опытным программистам.

    Пространство имен (namespacing)

    Ключевым моментом в создании хорошего JavaScript-кода для дальнейшего использования является тщательное управление пространством имен. В JavaScript существует единое глобальное пространство имен (объект window), и многие программисты (и даже некоторые библиотеки) засоряют его безо всякой надобности. Глобальные переменные - зло! Более благоразумные разработчики сводят к минимуму свое вторжение в это пространство, используя некоторые методы, например, модульную модель.
    jQuery вводит единственный объект в глобальное пространство имен - функцию/объект jQuery. Все остальное - это либо непосредственное свойство jQuery, либо метод объекта, возвращаемого вызовом функции jQuery.

    Что можно сказать об улучшениях языка? Большинство библиотек предоставляют некоторое подобие функций отображения, фильтрации и обрезания, которые, к несчастью, отсутствуют в тех движках JavaScript, которые включены в большинство браузеров. Некоторые библиотеки напрямую расширяют встроенные в JavaScript классы String и Array, но также не до конца безопасно. String.prototype и Array.prototype являются самостоятельными глобальными пространствами имен, и добавление в них каких-либо свойств влечет опасность коллизий, связанных с использованием одних и тех же имен переменных в разных контекстах.

    В jQuery имеется некоторое количество функций, расширяющих возможность JavaScript, но каждая из них является доступной только как свойство объекта jQuery: jQuery.each, jQuery.extend, jQuery.grep, jQuery.map, jQuery.merge и jQuery.trim. Они по определению не будут конфликтовать с каким-либо другим кодом.

    Печально известная функция $
    На самом деле jQuery вводит не один символ в глобальное пространство имен, есть еще и $: он используется как сокращение для jQuery. Это производится достаточно мягко: если вам снова требуется ваша прежняя функция $ (например, если у вас есть часть кода, основанная на Prototype), вы можете вызвать jQuery.noConflict(), чтобы вернуть свою старую функцию $.
    Если вам требуется ограничить использование функции $ для jQuery не опасаясь коллизий при каком-либо другом использовании глобальной функции $, документация по jQuery предлагает следующий способ:

    (function($) { // Внутри этого блока $ относится к jQuery // Изящно, правда? })(jQuery);

    Повсеместное использование $ в jQuery не более, чем хитроумный трюк. Но если рассматривать его только в контексте jQuery, то такое решение выглядит очень гибким.

    Выбираем элементы
    Каждый jQuery-оператор начинается с выбора одного или нескольких узлов DOM. Синтаксис селекторов jQuery (внутренний язык этой библиотеки) является интересным гибридом спецификаций CSS 1, 2, немного CSS 3, немного XPath и еще малость других расширений. Не будем углубляться в детали, просто приведем несколько полезных примеров:

    JQuery("div.panel") Все div с class="panel" jQuery("p#intro") Параграф с id="intro" jQuery("div#content a:visible") Все видимые ссылки внутри div с id="content" jQuery("input[@name=email]") Все поля ввода с name="email" jQuery("table.orders tr:odd") Все четные строки в таблице с class="orders" jQuery("a[@href^="http://"]") Все внешние ссылки (те, которые начинаются с http://) jQuery("p[a]") Все параграфы, в которых есть хотя бы одна ссылка

    Наибольший интерес из вышеописанного представляют:visible и:odd, которые являются специфичными только для jQuery. Стоит также отметить, что выбор атрибутов использует знак @, что более соответствует XPath нежели CSS 2.

    Язык селекторов весьма богат и очень похож на регулярные выражения, так что время, потраченное на его изучение, окупится сполна.

    Чего-нибудь с ними делаем
    Объект, который возвращают селекторы jQuery, является довольно интересным зверем. Он представляет собой набор DOM-элементов и ведет себя немного как массив: у него есть свойство length, к его элементам можно получить доступ по индексу и (что более важно) Firebug расценивает его именно как массив при отображении в своей консоли. Это не более, чем красивая иллюзия: набор элементов, на самом деле, - это объект jQuery, у которого есть большое число методов для выбора, изменения или расширения имеющегося набора.
    В jQuery имеется три разных категории методов: первые манипулируют со всеми элементами, которые подходят по шаблону, вторые возвращают значение от первого найденного элемента, и третьи изменяют саму выборку.

    Не будем перечислять все доступные методы (это можно посмотреть и на visualjquery.com), но приведем некоторые примеры. Если у вас есть Firebug, вы можете попробовать их самостоятельно: просто нужно воспользоваться закладкой «Вставить jQuery» (javascript:void(function(){var s=document.createElement(‘script’);
    s.src=’http://code.jquery.com/jquery-1.1.2.js’; document.getElementsByTagName(‘head’).appendChild(s);}())) для загрузки самой библиотеки для любой страницы, а затем вставить примеры кода в консоль Firebug (прим.: можно и без Firebug: достаточно загрузить jQuery с помощью указанной ссылки и вызвать приведенные примеры в адресной строке браузера, не забыв в начале javascript: и какой-либо alert в конце (чтобы на страницу не выводилось возвращаемое значение)).

    JQuery("div#primary").width(300); Выставляет ширину div id="primary" в 300 пикселей. jQuery("p").css("line-height", "1.8em"); Выставляет высоту строки в 1.8em для всех параграфов. jQuery("li:odd").css({color: "white", backgroundColor: "black"}); Применяет 2 CSS-правила для каждого пункта списка; заметьте, что функция css() может принимать объект таблицы стилей вместо двух строк. jQuery("a[@href^="http://"]").addClass("external").attr("target", "_blank"); Добавляет класс "external" для всех внешних ссылок (тех, что начинаются с http://), затем добавляет target="_blank", чтобы увеличить различие. В данном примере используется цепочка вызовов, описанная ниже. jQuery("blockquote").each(function(el) { alert(jQuery(this).text()) }); Для каждого тега blockquote на странице выводит сообщение (alert) с его текстовым содержанием (включая HTML-теги). jQuery("a").html("Нажми здесь!"); Заменяет весь текст в ссылках на странице призывом «Нажми здесь!». Ниже несколько примеров методов, которые возвращают значение от первого элемента, найденного по шаблону: var width = jQuery("div").width(); Какая ширина у первого div на странице? var src = jQuery("img").attr("src"); Какое значение у атрибута src у первой картинки на странице? var color = jQuery("h1").css("color"); Какой цвет у первого h1?

    Отметим удобную симметрию таких методов: они используются как для выставления атрибутов (когда принимают 2 аргумента или у передаваемого объекта несколько свойств), так и для прочтения значений этих атрибутов (если передается только один аргумент). Такая симметрия используется во всей библиотеке jQuery, что очень сильно облегчает запоминание API.
    Также есть несколько методов, которые изменяют весь набор найденных элементов. Многие из них также обеспечивают перемещение по DOM-дереву:

    JQuery("div").not("[@id]") Возвращает все div, у которых нет атрибута id. jQuery("h2").parent() Возвращает все элементы, которые являются непосредственными родителями h2. jQuery("blockquote").children() Возвращает все элементы, вложенные в blockquote. jQuery("p").eq(4).next() Находит пятый параграф на странице, потом находит следующий элемент (т.е. непосредственного соседа справа). jQuery("input:text:first").parents("form") Находит родительский элемент для формы, которая содержит первое поле input type="text" на странице. Опциональным параметром для parents() является другой селектор.

    Цепочки вызовов

    Команда разработчиков jQuery часто гордятся поддержкой в этой библиотеке цепочки вызовов, доходя до заявлений типа «jQuery создана для того, чтобы изменить ваш стиль программирования на JavaScript» прямо на главной странице. Эту технику находят скорее боковым ответвлением, чем дорогой в будущее, но вы можете использовать jQuery, избежав длинных цепочек вызовов.
    Цепочки можно использовать для нескольких интересных трюков. В дополнение к использованию набора DOM-выборок вы можете применить jQuery-метод end() для перемещения по стеку объектов и выхода из текущего контекста. Это немного сложно объяснить, но когда вы используете метод, который изменяет текущий (объектный) контекст (например, children() или filter()), вы можете использовать end() чуть позже, чтобы вернуться к предыдущей выборке. Jesse Skinner приводит хороший пример использования этой возможности в своем учебнике Делаем разработку на AJAX проще с jQuery:

    $("form#login") // прячем внутри формы все label с классом optional .find("label.optional").hide().end() // добавляем красную границу ко всем полям типа password в форме.find("input:password").css("border", "1px solid red").end() // добавляем обработчик на событие submit для формы.submit(function(){ return confirm("Вы действительно хотите отправить данные?"); });

    Все это большое преобразование займет всего одну строку. Оно находит форму, находит некоторые элементы внутри нее, применяет к ним изменения, возвращается обратно к форме и добавляет к ней обработчик события submit().

    Манипулируем с DOM

    В jQuery имеется несколько способов для объемных манипуляций с DOM. Первый является несколько неожиданным: функция jQuery может принимать в качестве аргумента кусок HTML-кода, который преобразуется в DOM-элемент (фактически, это просто строка):

    var div = jQuery(" Some text ");

    Можно использовать цепочку вызовов, чтобы добавить атрибуты к div, как только он был создан:

    var div = jQuery("Some text").addClass("inserted").attr("id", "foo"); Теперь добавим его к тегу body: div.appendTo(document.body) Или вставим его, используя селектор, в имеющийся код: div.prependTo("div#primary")

    Перехватываем события

    Все JavaScript-библиотеки нуждаются в методах для обработки событий, и jQuery не является исключением. Как и в случае attr() и css(), методы для событий могут служить двум целям: их вызов с функцией в качестве аргумента назначает обработчик события, вызов без аргумента эмулирует возникновение этого события:

    JQuery("p").click(function() { jQuery(this).css("background-color", "red"); }); Выставляем для всех параграфов обработчик клика мыши, по которому они становятся красными. jQuery("p:first").click() Эмулируем клик для первого параграфа на странице. Похожие функции используются для других событий браузера: mouseover, keyup и т.д. Следует заметить, что при вызове обработчика событий ключевое слово this ссылается на элемент, который это событие вызвал; использование jQuery(this) является расхожим приемом, чтобы вызвать методы jQuery для этого элемента. Пара функций, относящихся к событиям, заслуживает отдельного упоминания: jQuery("a").hover(function() { jQuery(this).css("background-color", "orange"); }, function() { jQuery(this).css("background-color", "white"); }); hover() является сокращением для сразу двух событий: onmouseover и onmouseout. jQuery("p").one("click", function() { alert(jQuery(this).html()); }); one() выставляет обработчик событий, который будет удален после первого своего вызова. В вышеприведенном примере всем параграфы должны сообщить (alert) свое содержание после первого клика по ним. jQuery также поддерживает собственные события через методы bind() и trigger() (подобные click()). Собственные события могут принимать аргументы, передаваемые при помощи массива в вызове trigger(): jQuery(document).bind("stuffHappened", function(event, msg) { alert("Что прозошло: " + msg); }); jQuery(document).trigger("stuffHappened", ["Привет!"]);

    Ненавязчивое (unobtrusive) программирование

    Лучшие веб-приложения - это те, которые могут функционировать и при отключенных скриптах, и лучшим методом для достижения этой цели будет ненавязчивый JavaScript, когда события назначаются элементам только после того, как вся HTML-страница у пользователя загрузится (для более подробной информации можно ознакомиться с ненавязчивым программированием и Hijax).
    В jQuery присутствует замечательная поддержка этого подхода. Во-первых, парадигма селекторов для выбора узла является основополагающей как для jQuery, так и для ненавязчивого программирования в целом. Во-вторых, jQuery обеспечивает решения для проблемы window.onload, основанной на исследованиях Dean Edwards по поводу работы события "DOM loaded" для различных браузеров. Вы можете выставить функцию-обработчик тогда, когда DOM уже будет к ней готов:

    jQuery(document).ready(function() { alert("DOM готов!"); });

    И даже больше, вы можете сократить эту запись, назначив вашу функцию напрямую jQuery():

    jQuery(function() { alert("DOM готов!"); });

    jQuery и AJAX

    У jQuery лучший API для работы с AJAX, который я когда-либо видел в больших библиотеках. Наиболее простая форма AJAX-вызова выглядит следующим образом:
    jQuery(‘div#intro’).load(‘/some/fragment.html’);

    Он выполнит GET-запрос к /some/fragment.html и вставит в div#intro HTML-код, который получит.
    Что если требуется что-то более сложное, например, вывести индикатор AJAX-загрузки? jQuery предоставляет набор собственных событий (ajaxStart, ajaxComplete, ajaxError и другие) для использования в случае необходимости. Также в этой библиотеки есть и более продвинутый API низкого уровня для сложных AJAX-взаимодействий:

    jQuery.get("/some/script.php", {"name": "Simon"}, function(data) { alert("Сервер ответил: " + data); }); // GET-запрос к /some/script.php?name=Simon jQuery.post("/some/script.php", {"name": "Simon"}, function(data) { alert("Сервер ответил: " + data); }); // POST-запрос к /some/script.php jQuery.getJSON("/some.json", function(json) { alert("JSON выдал: " + json.foo + " " + json.bar); }); // Возвращает и преобразует ответ от /some.json как JSON jQuery.getScript("/script.js"); // GET-запрос к /script.js и eval()

    Расширения

    Рассматривая весь этот набор функций в стандартной поставке, стоит заметить, что ужатый вариант jQuery занимает всего 20 КБ, и даже еще меньше, если применить архивирование (.gz). Дополнительная функциональность, выходящая за пределы это поставки, может быть организована с помощью расширений, которые могут (и так и делают) добавить новые методы к существующему объекту jQuery. При желании можно выполнить что-то вроде этого.

    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 вызван не будет.

    Предисловие

    Статья рассчитана на пользователей, хорошо знакомых с объектно-ориентированным программированием.

    Сейчас я расскажу о новой полезной возможности, которая была введена с ветки 5 в качестве развития ООП в PHP (по сравнению с веткой 4). Она называется «цепочки вызовов» (Method Chaining) и выглядит следующим образом:

    $object->method_a()->method_b()->method_c();

    Что же здесь на самом деле происходит? Начнем с того, что одно из (многих) изменений между PHP4 и PHP5 заключается в том, что теперь методы могут возвращать объекты. Это небольшое изменение позволяет по-новому использовать ООП. Итак, для того, чтобы пользоваться цепочками вызовами у вас должна быть возможность возвращать из метода объекты. Причем не существенно что это будет за объект — текущего класса, или совершенно другого.

    Обычное использование класса
    Начнем с самого обыкновенного класса и его использования.

    Class Person { private $m_szName; private $m_iAge; public function setName($szName) { $this->m_szName = $szName; } public function setAge($iAge) { $this->m_iAge = $iAge; } public function introduce() { printf("Hello my name is %s and I am %d years old.", $this->m_szName, $this->m_iAge); } }

    Теперь мы можем создать экземпляр класса Person и передать в объект имя и возраст. После чего, воспользовавшись методом introduce вывести на экран информацию о человеке:

    $peter = new Person(); $peter->setName("Peter"); $peter->setAge(23); $peter->introduce();

    В результате выполнения данного примера, на экран будет выведена строка:

    Hello my name is Peter and I am 23 years old.

    Использование цепочных вызовов
    На самом деле, отличий в коде будет совсем немного. Мы добавим всего 2 строки. Как же это будет работать? Очень просто! Нам нужно возвращать объект класса Person из каждого метода. Наш исходный класс будет выглядеть так:

    Class Person { private $m_szName; private $m_iAge; public function setName($szName) { $this->m_szName = $szName; return $this; //Возвращаем объект текущего класса } public function setAge($iAge) { $this->m_iAge = $iAge; return $this; //Возвращаем объект текущего класса } public function introduce() { printf("Hello my name is %s and I am %d years old.", $this->m_szName, $this->m_iAge); } }

    Пример использования модифицированного класса:

    $peter = new Person(); $peter->setName("Peter")->setAge(23)->introduce();

    На экран будет выведено абсолютно тоже самое, что и в изначальном примере.

    Как это работет
    Сначала мы выполняем $peter->setName(‘Peter’). Этот метод заполнит имя и вернет $this — текущий объект. Теперь нам нужно заполнить возраст. Следующее действие ->setAger(23). Поскольку мы записали вызов в цепочку, PHP интерпретирует это как «запустить метод setAge, принадлежащий тому, что вернул предыдущий метод». В данном случае Person ($peter).

    Абсолютно тоже самое происходит при вызове introduce. PHP запустит метод, обратившись к объекту возвращенному из предыдущей операции.

    Записывать методы в цепочку можно в любой последовательности и с любыми повторениями:

    $peter->setAge(23) ->setName("Peter") ->setName("Winifred") ->setAge(72) ->introduce();

    В данном случае на экран будет выведена строка

    Hello my name is Winifred and I am 72 years old.



    Есть вопросы?

    Сообщить об опечатке

    Текст, который будет отправлен нашим редакторам: