Власть $.deferred

Одним из наиболее важных аспектов в создании гладких и отзывчивый приложений HTML5 является синхронизация между всеми различными частями приложения, такие как получение данных, обработка, анимация и элементы пользовательского интерфейса.
Основное отличие от настольного ПК является то, что браузеры не предоставляют доступ к потоковой модели и не могут обеспечить единый поток, доступ к пользовательским интерфейсом (например, DOM). Это означает, что вся логика приложения доступа и изменения элементов интерфейса пользователя, всегда находится в том же потоке. Важно сохранить все нюансы работы приложения, пользуясь любыми асинхронными возможностями браузера.

Асинхронный интерфейс

Есть ряд асинхронных API, такие как XHR ( XMLHttpRequest или ‘AJAX’) API-интерфейсов, а также IndexedDB , SQLite, HTML5 Web Workers, и HTML5 GeoLocation API, и это не всё. Даже некоторые действия, связанные с DOM работают асинхронно, например анимации CSS3 через события transitionEnd.

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

Например, код использующий API XHR, события на основе асинхронных API, будет выглядеть следующим образом:



// Create the XHR object to do GET to /data resource
  var xhr = new XMLHttpRequest();
  xhr.open("GET","data",true);

  // register the event handler
  xhr.addEventListener('load',function(){
  if(xhr.status === 200){
  alert("We got data: " + xhr.response);
  }
  },false)

  // perform the work
  xhr.send();


Событие CSS3 transitionEnd еще один пример на основе асинхронных API.



// get the html element with id 'flyingCar'
var flyingCarElem = document.getElementById("flyingCar");

// register an event handler
// ('transitionEnd' for FireFox, 'webkitTransitionEnd' for webkit)
flyingCarElem.addEventListener("transitionEnd",function(){
  // will be called when the transition has finished.
  alert("The car arrived");
});

// add the CSS3 class that will trigger the animation
// Note: some browers delegate some transitions to the GPU , but
//       developer does not and should not have to care about it.
flyingCarElemen.classList.add('makeItFly')


Другие API такие как SQLite и HTML5 Geolocation с обратным вызовом, а это означает, что разработчик передает функции в качестве аргумента, который будет вызван обратно на базовую реализацию с соответствующей резолюцией.

Например, для HTML5 Geolocation , код выглядит следующим образом:



// call and pass the function to callback when done.
  navigator.geolocation.getCurrentPosition(function(position){
  alert('Lat: ' + position.coords.latitude + ' ' +
  'Lon: ' + position.coords.longitude);
  });


В этом случае, мы просто вызвать метод и передаём функцию, которая будет вызвана обратно с результатом. Это позволяет браузеру реализовать функциональности синхронно или асинхронно и дать единый API для разработчиков, независимо от деталей реализации.

Асинхронное приложение READY

Хорошо структурированные приложения с асинхронным API, особенно, когда они делают любой I/O или интенсивной вычислительной обработки. Например, API чтобы получить данные, должен быть асинхронным, и НЕ должны выглядеть так:



// WRONG: this will make the UI freeze when getting the data
  var data = getData();
  alert("We got data: " + data);


Эта конструкция API для блокировки требует   GetData() , который будет замораживать пользовательский интерфейс, пока нет данных. Если данные являются локальными в контексте JavaScript, это не проблема, но если данные должны быть получены из сети или даже локально в SQLite или в индекс это может оказать серьезное влияние на работу пользователя.

Например упрощенный  GetData() API:



getData(function(data){
   alert("We got data: " + data);
});


В этом подходе хорошо то, что он асинхронный ориентированно с самого начала и позволяет основные API, для решения, если они должны быть асинхронными или нет на более позднем этапе.

Обратите внимание, что не все потребности приложений API должны быть асинхронными. Любой API, типа I /O или тяжелой обработки (все, что может занять больше времени, чем 15 мс) должна подвергаться асинхронно с самого начала, даже если первая реализация синхронная.

Обработка отказов

Традиционному способу try/catch справляться с неудачами на самом деле не правильно это работа anymore, ошибки как правило, происходят в другом потоке. Следовательно, вызов должен иметь структурированный способ сообщить вызываемому, что пойдет не так во время обработки.

Часто в основе событий асинхронных API , это достигается путем применения запроса кода события или объекта при получении события. Для обратного вызова на основе асинхронных интерфейсов API, лучшие иметь второй аргумент, который принимает функцию, которая будет вызываться в случае аварии с соответствующей информацией об ошибках в качестве аргумента.
Наш GetData вызов будет выглядеть так:



// getData(successFunc,failFunc);
  getData(function(data){
  alert("We got data: " + data);
  }, function(ex){
  alert("oops, some problem occured: " + ex);
  });


Всё вместе $.deferred

Одним из ограничений подхода обратного вызова в том, что он может быть большим, даже для средней сложности логики синхронизации.

Например, если вам нужно ждать два асинхронных API, сложность кода может быстро расти.



/ first do the get data.
getData(function(data){
  // then get the location
  getLocation(function(location){
    alert("we got data: " + data + " and location: " + location);
  },function(ex){
    alert("getLocation failed: "  + ex);
  });
},function(ex){
  alert("getData failed: " + ex);
});


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

К счастью, есть довольно старая модель, называемая Promises , а также надежные и современные реализации с jQuery называется $.deferred, что обеспечивает простое и мощное решение для асинхронного программирования.

Чтобы сделать его простым, шаблон Promise определяет, что асинхронное API возвращает объект  Promise , который является своего рода обещание «что результат будет решен с соответствующими данными.» Чтобы получить разрешение, вызывающий получает объект Promise  и вызывает  done(successFunc(data))  , который говорит объекту Promise  называют это  successFunc , когда «данные» не будут решены.

Так, например GetData вызова, становится вроде этого:



// get the promise object for this API
  var dataPromise = getData();

  // register a function to get called when the data is resolved
  dataPromise.done(function(data){
  alert("We got data: " + data);
  });

  // register the failure function
  dataPromise.fail(function(ex){
  alert("oops, some problem occured: " + ex);
  });

  // Note: we can have as many dataPromise.done(...) as we want.
  dataPromise.done(function(data){
  alert("We asked it twice, we get it twice: " + data);
  });


Здесь мы получили объект dataPromise , а затем вызвали методом  .done  чтобы зарегистрировать функцию мы хотим получить возврат, когда данные будут решены. Мы также можем вызывать способ .fail  он справиться с возможной неудачей. Обратите внимание, что мы можем иметь столько  .done  или  .fail  вызовов, сколько нам нужно, так как лежащий в основе реализация Promise (JQuery код) будет обрабатывать регистрацию и обратные вызовы.

С помощью этой модели относительно просто в реализации для более продвинутых кодов синхронизации и jQuery уже предоставляет наиболее распространенные например $.when

Вот вложенные  GetData  /  GetLocation  обратного вызова:



/ assuming both getData and getLocation return their respective Promise
  var combinedPromise = $.when(getData(), getLocation())

  // function will be called when both getData and getLocation resolve
  combinePromise.done(function(data,location){
  alert("We got data: " + dataResult + " and location: " + location);
  });


И красота всего этого является то, что jQuery.deferred делает его очень легким для разработчиков для реализации асинхронной функции. Например, GetData может выглядеть так:



function getData(){
  // 1) create the jQuery Deferred object that will be used
  var deferred = $.Deferred();

  // ---- AJAX Call ---- //
  XMLHttpRequest xhr = new XMLHttpRequest();
  xhr.open("GET","data",true);

  // register the event handler
  xhr.addEventListener('load',function(){
    if(xhr.status === 200){
      // 3.1) RESOLVE the DEFERRED (this will trigger all the done()...)
      deferred.resolve(xhr.response);
    }else{
      // 3.2) REJECT the DEFERRED (this will trigger all the fail()...)
      deferred.reject("HTTP error: " + xhr.status);
    }
  },false)

  // perform the work
  xhr.send();
  // Note: could and should have used jQuery.ajax.
  // Note: jQuery.ajax return Promise, but it is always a good idea to wrap it
  //       with application semantic in another Deferred/Promise
  // ---- /AJAX Call ---- //

  // 2) return the promise of this deferred
  return deferred.promise();
}


Поэтому, когда getData () вызывается, прежде всего, он создает новый jQuery.Deferred объект (1), а затем возвращает его Promise (2), так что вызывающий сможет сделать его зарегистрированным. Потом, когда вызов XHR возвращает, он решает deferred (3.1), либо отвергнуть его (3.2). Делать deferred.resolve будет включать все done(…) функций, и другие функции обещают и призывая deferred.reject будем называть все функции fail ().

Варианты использования

Вот несколько хороших вариантов использования, где deferred может быть очень полезен:

Доступ к данным: Предоставление доступа к данным API, как $ deferred часто правильный дизайн. Это очевидно для удаленных данных, а синхронные вызовы удаленных бы полностью разрушить пользовательского опыта, но также верно для локальных данных, как это часто в нижнем уровне API (например, SQLite и IndexedDB) являются асинхронными .deferred API»s $.when являются чрезвычайно мощными, чтобы синхронизировать и цепь асинхронных подзапросов.

Пользовательский интерфейс Анимации:  Дирижером одной или несколько анимаций с transitionEnd событий может быть весьма утомительным, особенно когда анимация смешанные из CSS3 анимации и JavaScript (как это часто бывает). Упаковка анимации функции отложенного может значительно уменьшить сложность кода и улучшить гибкость. Даже простой общий функцию оболочку, как cssAnimation (имя класса), который будет возвращать Promise объект, который получает решен на transitionEnd может быть большим спором.

UI компонентов Дисплей:  Это немного более продвинутый, но передовые компоненты HTML структуры должны также использовать deferred. Не вдаваясь слишком в подробности (это будет предметом другой должности), когда приложение должно отображать различные части пользовательского интерфейса, имеющего жизненного цикла эти компоненты заключены в отложенных обеспечивает больший контроль сроков.

Любой API браузера асинхронные:  Для нормализации цели, он часто является хорошей идеей, чтобы обернуть браузере API вызовы, deferred. Это займет буквально от 4 до 5 строк кода каждого, но позволит значительно упростить код приложения. Как показывают в приведенном выше GetData / GetLocation псевдо-код, что позволяет приложениям коды, чтобы один были асинхронной модели для всех типов API (браузеры, приложения специфику, и соединение).

Кэш:  Это своего рода побочный эффект, но может быть очень полезен в некоторых случаях. Потому что обещание API (например,.done(..) и .fail(..)) может быть вызван до или после асинхронных вызовов выполняется, отложенные объект может быть использован в качестве решения для кэширования асинхронных вызовов. Например, CacheManager может просто следить за deferred для данной просьбы, и вернуть обещание соответствующего deferred если он не будет признан недействительным. Вся прелесть в том, что абонент не должен знать, если вызовы уже решены или находятся в процессе решения, его функция обратного вызова будет вызвана точно так же.

Заключение

В то время как концепция $.deferred проста, это может занять время, чтобы получить для его хорошее решение. Однако, учитывая характер браузера окружающей среды, освоение асинхронного программирования в JavaScript является обязательным для любого серьезного разработчика приложений HTML5 и обещание картины (и JQuery реализации) огромные средства, чтобы сделать асинхронное программирование, надежным и мощным.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML -теги и атрибуты: <a href= http://pixelcom.crimea.ua/"" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>