Опубликован: 28.02.2016 | Уровень: для всех | Доступ: платный
Лекция 9:

Объект Deferred и побратимы

Работа с объектом "Deferred" это уже высший пилотаж, это "mad skills" заставлять асинхронный JavaScript работать синхронно. Давайте посмотрим как он работает (данный код можно скопировать в консоль и выполнить на любой странице, где подключен jQuery):

// инициализация Deferred объекта
// статус "ожидает выполнение"
var D = $.Deferred();
// подключаем обработчики
D.done(function() { console.log("first") });
D.done(function() { console.log("second") });
// изменяем статус на "выполнен успешно"
// для этого вызываем resolve()
// наши обработчики будут вызваны в порядке очереди,
// но они не ждут друг-друга
D.resolve();
// данный обработчик подключён слишком поздно, и будет вызван сразу
D.done(function() { console.log("third") });

Кроме сценариев с "happy end", есть ещё и грустные истории, когда всё пошло не так как нам бы хотелось:

// инициализация Deferred объекта
var D = $.Deferred();
// подключаем обработчики
D.done(function() { console.log("done") });
D.fail(function() { console.log("fail") });
// изменяем статус на "выполнен с ошибкой"
D.reject();
// в консоли нас будет ожидать лишь "fail" :(

Для ускорения написания кода существует ещё метод "then()", который позволяет вешать одновременно обработчики всех типов:

D.then(function() { console.log("done") },
function() { console.log("fail") });

Становится ли от этого код читаемым – сомневаюсь, но метод есть, и может пригодится.

Ещё упомяну метод "always()" – он добавляет обработчики, которые будут выполнены вне зависимости от выбранного сценария. Чтобы не путаться в перечисленных методах приведу блок-схему:

В качестве аргументов методов "done()" и "fail()"может быть произвольное множество callback-функций. При вызове "resolve()" и "reject()" можно передать произвольные данные в зарегистрированные callback-функции для дальнейшей работы. Кроме того, существуют еще методы "resolveWith()" и "rejectWith()", они позволяют изменять контекст вызываемых callback-функции (т.е. внутри них "this" будет смотреть на указанный контекст)

Отдельно хотел отметить, что если вы собираетесь передать Deferred объект "на сторону", чтобы "там" могли повесить свои обработчики событий, но не хотите потерять контроль, то возвращайте не сам объект, а результат выполнения метода "promise()" – это фактически будет искомый объект в режиме "read only".

Теперь покажу хитрый метод "$.when()":

$.when(
$.ajax("/ajax/example.json"),
$("article").slideUp(200)
).then(function(){
alert("All done");
}, function(){
alert("Something wrong");
})

Поясню происходящее – AJAX запрос и анимация стартуют одновременно, когда и тот и другой завершат свою работу, будет вызвана функция, которую мы передаём в качестве аргумента в метод "then()" (одна из двух, в зависимости от исхода происходящего). Для обеспечения работы этой "магии" методы "when()", "ajax()" и "animate()" реализуют интерфейс Deferred. Пример работы

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример работы с $.when</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">

            <a href="deferred.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="deferred.pipe.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$.when(
    $(<span>'article img'</span>).slideUp(1000),
    $(<span>'article p'</span>).hide(3000)
).done(function(){
     $(<span>'article'</span>).slideUp(500)
})</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы метода $.when()</h1>
            <h2>Поддержка анимации</h2>
        </header>
        <article id="stick" class="box">
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

Теперь можно и заумно – метод "when()" возвращает проекцию Deferred объекта, принимает в качестве параметров произвольное множество Deferred объектов, когда все из них отработают, объект "when" изменит своё состояние в "выполнено", с последующим вызовом всех подписавшихся.

А ещё, кроме поведения "ждём всех", с помощью Deferred можно выстраивать цепочки вызовов – "живые очереди":

$.ajax('ajax/example.json')
.then(function(){
// подождём окончания AJAX запроса
return $('article img').slideUp(2000)
})
.then(function(){
// подождём пока спрячутся картинки
return $('article p').slideUp(2000)
})
.then(function(){
// подождём пока спрячутся параграфы
return $('article').hide(2000);
})
.done(function(){
// всё сделано шеф
});

Подобное поведение можно воспроизвести используя лишь animate, но нам же хочется заглянуть чуть-чуть поглубже (до версии 1.8 тут шла речь о методе "pipe()", а теперь о "then()")

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример цепочек вызовов $.Deferred</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="when.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="callbacks.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
<pre><code contenteditable="true">$.ajax(<span>'ajax/example.json'</span>)
    .then(function(){
        appendOut(<span>'Ajax\n'</span>);
        return $(<span>'article img'</span>).slideUp(2000)
    })
    .then(function(){
        appendOut(<span>'Images\n'</span>);
        return $(<span>'article p'</span>).slideUp(2000)
    })
    .then(function(){
        appendOut(<span>'Paragraph\n'</span>);
        return $(<span>'article'</span>).hide(2000);
    })
    .done(function(){
        appendOut(<span>"All Ok"</span>);
    });
</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример построение цепочек $.Deferred</h1>
            <h2>C animate(), метод pipe() не подружился :(</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-bumblebee-tumb.jpg" alt="Bumblebee" class="left" width="200"/>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum,
            lectus eu varius consectetur, libero velit hendrerit augue, ut posuere enim neque
            in libero. Donec eget sagittis nibh. Suspendisse sed tincidunt urna. Cras quis
            euismod neque. Maecenas auctor ultricies posuere. Pellentesque luctus pulvinar dui
            eget semper. Donec sodales odio eu sapien varius luctus. Donec dictum feugiat diam
            at malesuada. Sed nec massa in augue condimentum faucibus quis ut diam. Quisque
            nisl sem, semper nec vulputate vel, mattis sit amet justo. Aliquam purus felis,
            tempor at scelerisque quis, tincidunt in neque. Etiam ut risus diam. Pellentesque
            fermentum risus id elit feugiat cursus. Ut fringilla dictum diam, sed iaculis
            lorem pulvinar ut. Cras vel elit id velit commodo viverra sit amet vel orci.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-chamomile-tumb.jpg" alt="Chamomile" class="left" width="200"/>
            Duis in vestibulum sem. Cras euismod tincidunt dui, et scelerisque tellus condimentum vel.
            Maecenas et urna sit amet risus fermentum rhoncus nec porttitor ligula. Maecenas sit amet
            turpis enim, ut iaculis est. Duis feugiat, lacus id placerat porttitor, lorem augue gravida
            nisi, eu porta eros risus et lectus. Maecenas vestibulum nunc vel ipsum tincidunt sit amet
            blandit sapien bibendum. Proin vel vulputate nisl. Duis tempor imperdiet placerat. Pellentesque
            faucibus consequat magna, et bibendum nisl egestas non. Pellentesque sit amet mattis augue.
            Aenean at diam tincidunt purus sollicitudin gravida non in nisi. Fusce bibendum, magna in
            adipiscing mattis, sem risus fringilla mi, nec gravida lectus lectus at nibh. Suspendisse
            adipiscing elementum laoreet. Suspendisse sem erat, varius quis aliquet vitae, dapibus sed
            nibh. Nullam iaculis sem at mauris faucibus in vestibulum libero pretium. Aliquam eu turpis
            libero. Fusce et ultrices lectus.</p>
        </article>
        <article>
            <h2>Article</h2>
            <p>
                <img src="images/photo-maple-leaf-tumb.jpg" alt="Maple Leaf" class="left" width="200"/>
            Ut consequat commodo mauris, eu dignissim justo congue vel. Etiam commodo tincidunt diam,
            laoreet ullamcorper sapien egestas quis. Etiam auctor rutrum ante, at tincidunt elit lacinia
            non. Pellentesque molestie tellus sit amet est sodales nec rutrum leo pharetra. Donec lacinia
            ipsum vitae massa accumsan ullamcorper. Maecenas commodo lacus turpis. Proin sit amet mauris
            sem, imperdiet faucibus lorem. Fusce ullamcorper consectetur ligula vel pretium. Sed et elit
            vitae orci adipiscing condimentum id sed turpis. Morbi ultrices feugiat ullamcorper. Fusce at
            magna dolor. Sed sit amet risus massa, quis imperdiet libero. Proin justo purus, sodales nec
            cursus et, sollicitudin at nulla. Vivamus eget nibh tellus, sit amet facilisis ante.</p>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

В данном примере мы вызываем метод "then()", которому скормлена callback-функция, которая должна возвращать объект Deferred, это необходимо для соблюдения порядка в очереди – попробуйте убрать в примере один "return", и вы заметите, что следующая анимация наступит не дождавшись завершения предыдущей.

На этом возможности Deferred ещё не завершились, есть ещё связка методов "notify()" и "progress()" – первый шлёт послания в callback-функции, которые зарегистрированы с помощью второго. Приведу наглядный код для демонстрации (копи-паст в консоль, и смотрите что получается):

var D = $.Deferred();
var money = 100; // наш бюджет
// съём денежки
D.progress(function($){
console.log(money + " - " + $ + " = " + (money-$));
money -= $;
if (money < 0) { // деньги закончились
this.reject();
}
});

// тратим деньги
setTimeout(function(){ D.notify(40); }, 500); // покупка 1
setTimeout(function(){ D.notify(50); }, 1000); // покупка 2
setTimeout(function(){ D.notify(30); }, 1500); // покупка 3
D.done(function(){ console.info("All Ok") });
D.fail(function(){ console.error("Insufficient Funds") });

Испытайте всю мощь Deferred в примере

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Работаем с $.Deferred</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        .loading {
            background: url('images/ajax-loader.gif') no-repeat right;
        }
        #images div {
            height: 216px;
            width: 310px;
            float:left;
            overflow: hidden;
            margin: 4px;
            border: 1px solid #ccc;
            border-radius: 4px;
            -moz-border-radius: 4px;
            -webkit-border-radius: 4px;
        }
        article div img {
            min-height: 216px;
            min-width: 310px;
            border:0;
            padding:0;
            margin:0;
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        var Flickr = {
            search:function(query) {
                showLoader();
                var def = $.Deferred();
                $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", {
                            tags: query,
                            tagmode: "any",
                            format: "json"
                        },
                        def.resolve
                );
                return def.promise();
            }
        };
        function buildImages(data) {
            var def = $.Deferred();
            var limit = 4;
            var loaded = 0;
            $.each(data.items, function(i, item){
                if ( i == limit ) return false;
                var img         = new Image();
                    img.onload  = img.onerror = function() {
                        // изменяем прогресс загрузки
                        def.notify(1)
                    };
                    img.src     = item.media.m;
                var div = $('<div></div>').append(img);
                $(div).prependTo("#images");
            });
            def.progress(function() {
                loaded ++;
                if (limit == loaded) {
                    def.resolve();
                }
            });
            def.done(hideLoader);
        }
        function showLoader() {
            $('h1').addClass('loading');
            $('#images').slideUp(function(){
                $(this).html('')
            });
        }
        function hideLoader() {
            $('h1').removeClass('loading');
            $('#images').slideDown();
        }
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
			<a href="index.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <a href="when.html" title="go next" class="button alignright" rel="next">Next →</a>
            <hr/>
            <pre><code contenteditable="true">Flickr
    .search(<span>'orange'</span>)
    .then(buildImages)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы с $.Deferred</h1>
            <h2>С использованием AJAX и ожиданием подгрузки картинок. Код функции buildImages вы найдёте в исходном коде данной страницы</h2>
        </header>
        <article>
            <h2>Flickr</h2>
            <p>Попробуем загрузить картинки используя AJAX+JSONP. По получению ответа с сервера создаём объекты «Image» и ждём их загрузки,
            и лишь после этого отображаем готовый результат</p>
            <div id="images">
            </div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
        </script>
	</div>
</body>
</html>

Callbacks

Callbacks – это крутой объект – он позволяет составлять списки функций обратного вызова, а также даёт бразды правления над ними. Работать с ним проще нежели с Deferred, тут нет разделения на позитивный и негативный сценарии, лишь стек функций, который будет выполнен по команде "fire()":

var C = $.Callbacks();
C.add(function(msg) {
console.log(msg+" first")
});
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Go");
>>>
Go first
Go second

— А в чём сила, брат?

— В аргументах

По умолчанию, вы можете прямо из консоли вызвать метод "fire()" снова и снова, и будете получать один и тот же результат раз за разом. А можно задать поведение Callbacks через флаги:

  • once – все функции будут вызваны единожды (аналогично как в Deferred).
  • memory – сохранять значение с последнего вызова "fire()", и скармливать его в ново-зарегистрированные функции обратного вызова, и лишь потом обрабатывает новое значение (в Deferred именно так).
  • unique – список функций обратного вызова фильтруется по уникальности
  • stopOnFalse – как только какая-нить функция вернёт "false", процесс запуска остановится

Наверное, будет лучше с примерами, вот "once":

var C = $.Callbacks("once");
C.add(function(msg) {
console.log(msg+" first")
});
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Go");
C.fire("Again"); // не даст результата, только Go
>>>
Go first
Go second

C "memory" посложнее, будьте внимательней:

var C = $.Callbacks("memory");
C.add(function(msg) {
console.log(msg+" first")
});
C.fire("Go");
C.add(function(msg) {
console.log(msg+" second")
});
C.fire("Again");
>>>
Go first
Go second // без флага, этой строчки не было бы
Again first
Again second

Пример с уникальностью прост до безобразия:

var C = $.Callbacks("unique");
var func = function(msg) {
console.log(msg+" first")
};
C.add(func);
C.add(func); // эта строка не повлияет на результат
C.fire("Go"); // только Go first
>>>
Go first

Флаг "stopOnFalse":

var C = $.Callbacks("stopOnFalse");
C.add(function(msg) {
console.log(msg+" first");
return false; // вот он – роковой false
});
C.add(function(msg) { console.log(msg+" second") });
C.fire("Go"); // только Go first
>>>
Go first

Перечисленные флаги можно комбинировать и получать интересные результаты, а можно не получать, а лишь посмотреть на пример:

<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Пример использования с $.Callbacks</title>
    <link rel="profile" href="http://gmpg.org/xfn/11"/>
    <link rel="shortcut icon" href="http://anton.shevchuk.name/favicon.ico"/>
    <link rel="stylesheet" href="css/styles.css"/>
    <style>
        article {
            overflow: visible;
            min-height: 120px;
            margin: 10px;
            position: relative;
        }
        #car {
            width: 100px;
            height: 100px;
            position: absolute;
            background: url(images/car.png) 50% 50% no-repeat;
            -webkit-transition: all 0.2s ease-in-out;
            -moz-transition: all 0.2s ease-in-out;
            -o-transition: all 0.2s ease-in-out;
            -ms-transition: all 0.2s ease-in-out;
            transition: all 0.2s ease-in-out;
        }
        .up {
            -webkit-transform: rotate(0deg);
            -moz-transform: rotate(0deg);
            -o-transform: rotate(0deg);
            -ms-transform: rotate(0deg);
            transform: rotate(0deg);
        }
        .down {
            -webkit-transform: rotate(180deg);
            -moz-transform: rotate(180deg);
            -o-transform: rotate(180deg);
            -ms-transform: rotate(180deg);
            transform: rotate(180deg);
        }
        .right {
            -webkit-transform: rotate(90deg);
            -moz-transform: rotate(90deg);
            -o-transform: rotate(90deg);
            -ms-transform: rotate(90deg);
            transform: rotate(90deg);
        }
        .left {
            -webkit-transform: rotate(270deg);
            -moz-transform: rotate(270deg);
            -o-transform: rotate(270deg);
            -ms-transform: rotate(270deg);
            transform: rotate(270deg);
        }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/code.js"></script>
    <script>
        var C = $.Callbacks();
        var car;
        $(function(){
            car = $('#car');
            C.add(function(speed){
                appendOut('\nSpeed 200px per '+speed+' ms: ')
            });
            $(document).keydown(function(e){
                // 37 - left
                // 38 - up
                // 39 - right
                // 40 - down
                switch (e.keyCode) {
                    case 37:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'left')
                                       .dequeue();
                            });
                            car.animate({left:'-=200'}, speed);
                            appendOut('← ');
                        });
                        appendOut('\nAdded ←');
                        break;
                    case 38:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'up')
                                       .dequeue();
                            }).animate({top:'-=200'}, speed);
                            appendOut('↑ ');
                        });
                        appendOut('\nAdded ↑');
                        break;
                    case 39:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'right')
                                       .dequeue();
                            });
                            car.animate({left:'+=200'}, speed);
                            appendOut('→ ');
                        });
                        appendOut('\nAdded →');
                        break;
                    case 40:
                        C.add(function(speed){
                            car.queue(function(){
                                $(this).attr('class', 'down')
                                       .dequeue();
                            });
                            car.animate({top:'+=200'}, speed);
                            appendOut('↓ ');
                        });
                        appendOut('\nAdded ↓');
                        break;
                }
            })
        });
    </script>
</head>
<body>
    <div id="content" class="wrapper box">
        <menu label="Try...">
            <a href="deferred.pipe.html" title="go prev" class="button alignleft" rel="prev">← Back </a>
            <a href="index.html" title="back to Index" class="button alignleft" rel="index">Index §</a>
            <a href="#" title="reload" class="button alignleft" onclick="window.location.reload();return false">Reload ¤</a>
            <hr/>
            <pre><code><em>// use button up arrow: ↑</em>
C.add(function(speed){
    car.animate({top:'-=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button down arrow: ↓</em>
C.add(function(speed){
    car.animate({top:'+=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button right arrow: →</em>
C.add(function(speed){
    car.animate({left:'+=200px'}, speed)
})</code></pre>
            <pre><code><em>// use button left arrow: ←</em>
C.add(function(speed){
    car.animate({left:'-=200px'}, speed)
})</code></pre>
<pre><code contenteditable="true">C.fire(200)</code></pre>
            <button type="button" class="code">Run Code</button>
        </menu>
        <header>
            <h1>Пример работы $.Callbacks</h1>
            <h2>На практике мне ещё не приходилось использовать, поэтому пример немного надуманный - запрограммируйте поездку
                машинки используя стрелки на клавиатуре, а затем запустите программу, указав скорость</h2>
        </header>
        <div id="output">
            <h3>Output</h3>
            <pre></pre>
        </div>
        <article>
            <div id="car"></div>
        </article>
        <footer>
            ©copyright 2014 Anton Shevchuk — <a href="http://anton.shevchuk.name/jquery-book/">jQuery Book</a>
        </footer>
        <script type="text/javascript">
            var _gaq = _gaq || [];
            _gaq.push(['_setAccount', 'UA-1669896-2']);
            _gaq.push(['_trackPageview']);
            (function() {
             var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
             var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
            })();
         </script>
	</div>
</body>
</html>

Из истории: объект "Deferred" отпочковался от метода "ajax()" в результате рефакторинга версии 1.5. Шло время, появлялись новые версии jQuery, и вот новый виток рефакторинга – результатом стало отделение "Callbacks" от "Deferred" в версии 1.7, таким образом в текущей версии библиотеки метод "ajax()" работает с объектом "Deferred", который является надстройкой над "Callbacks". Дабы не вносить путаницу в терминологию, я использую определение "Deferred Callbacks" и при работе с "Callbacks", ибо колбэков много, и каждый раз уточнять, что я говорю именно "о том самом" - дело достаточно утомительное.

Статьи по данной теме:

Наталья Маркова
Наталья Маркова
Ярослав Гаевой
Ярослав Гаевой

10 марта 2016 c 20:13 до 22:39 я сдавал экзамен. Однако, за два месяца статус не изменился: "Задание не проверено"

Когда ожидать проверки?

Руслан Жанбосынов
Руслан Жанбосынов
Россия
Дмитрий Молокоедов
Дмитрий Молокоедов
Россия, Новосибирск, НГПУ, 2009