Deferred-объект

jQuery позволяет обрабатывать не только браузерные события (click, keypress и т.п.), а вообще любые действия (конец выполнения функции и т.п.). Для этого используются специальные Deferred-объекты, которые создаются при помощи конструктора jQuery.Deferred(). Deferred-объект может объединять несколько функций в очередь, запускать эту очередь, а также может использоваться для переключения состояния любой синхронной или асинхронной функции (из состояния ожидание в выполнено или отклонено).

Создание объекта

Для создания Deferred-объекта используется конструктор $.Deferred(). Оператор new использовать не обязательно. Примеры:

/* Создание стандартного объекта */
var defr_1 = $.Deferred();

/* Создание Deferred-объекта с обработчиком состояния "выполнено" */
var defr_2 = $.Deferred(function() {
  this.done(callback);
});

Применение

Сразу после создания Deferred-объект находится в состоянии ожидание. Данный объект обладает методами для изменения своего состояния на выполнено (методы resolve(), resolveWith()) или отклонено (методы reject(), rejectWith()). Изменить своё состояние Deferred-объект может только один раз, то есть после вызова любого из этих методов дальнейшее их использование игнорируется.

Кроме того, можно установить обработчики, которые будут запускаться при смене состояния объекта. Для установки обработчиков успешного выполнения используется метод done(). Обработчики состояния отклонено устанавливаются с помощью методов catch() или fail(). Метод always() устнавливает обработчики, которые запускаются при переходе в любое состояние. Обработчики запускаются в той последовательности, в которой они были заданы. Если обработчик установлен после смены состояния Deferred-объекта, он будет выполнен немедленно.

С помощью метода progress() можно установить обработчики, которые будут выполняться только в состоянии ожидание. Для их вызова используются методы notify() или notifyWith(). Так как эти методы не меняют состояние Deferred-объекта, их можно вызывать любое количество раз, но только до смены состояния. Пример:

<html>
<head>
  <title>Deferred-объекты в jQuery</title>
  <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
  <form autocomplete="off">
    <table>
      <tr>
        <th>Вопросы теста</th>
        <th>Да/Нет</th>
      </tr>
      <tr>
        <td>Вопрос 1</td>
        <td>
          <input type="radio" name="test_1" value="1">
          <input type="radio" name="test_1" value="0">
        </td>
      </tr>
      <tr>
        <td>Вопрос 2</td>
        <td>
          <input type="radio" name="test_2" value="1">
          <input type="radio" name="test_2" value="0">
        </td>
      </tr>
      <tr>
        <td>Вопрос 3</td>
        <td>
          <input type="radio" name="test_3" value="1">
          <input type="radio" name="test_3" value="0">
        </td>
      </tr>
    </table>
  </form>
  <p>Выполнено: 0%</p>
</body>
</html>
 
<script>
var completed = 0; /* кол-во пройденных вопросов */
/* Создание Deferred-объекта */
var defr = $.Deferred();
defr
  /* Обработчик состояния 'ожидание' */
  .progress(function() {
    $('p').html('Выполнено: ' + Math.round(completed * 100 / 3) + '%');
  })
  /* Обработчики состояния 'выполнено' */
  .done(function() {
    $('p').html('Тест пройден.');
  })
  .done(function() {
    alert('Результат теста: ...');
  });

$('input').on('change', function () {
  $(this).siblings().attr('disabled', '');
  if (++completed != 3) {
    /* Обработать состояние 'ожидание' */
    defr.notify();
  } else {
    /* Изменить состояние на 'выполнено' при ответе на все вопросы */
    defr.resolve();
  }
});
</script>
Вопросы теста Да/Нет
Вопрос 1
Вопрос 2
Вопрос 3

Выполнено: 0%

Особенности использования

Deferred-объект позволяет использовать цепочки методов, как для jQuery-объекта, но он имеет свои собственные методы. Методы jQuery-объектов к Deferred-объектам не применимы и наоборот.

Чтобы обработать изменение состояния сразу нескольких Deferred-объектов, используется функция jQuery.when(). Она создаёт новый Deferred-объект, который переходит в состояние выполнено, если все указанные объекты перейдут в него. Если хотя бы один из переданных объектов перейдёт в состояние отклонено, тогда общий Deferred-объект тоже перейдёт в него.

С помощью метода deferred.promise() можно создать заместителя Deferred-объекта. Это тот же объект, за исключением того, что в нём отсутствуют методы, изменяющие состояние объекта (resolve(), reject(), notify(), resolveWith(), rejectWith() и notifyWith()). Promise-объекты должны использоваться в тех случаях, когда необходимо защитить Deferred-объекты от несанкционированного изменения состояния. Например, jqXHR-объект является Deferred-объектом, а точнее его Promise-версией. Его состояние нельзя изменить принудительно, так как он не имеет соответствующих методов.