Javascript.RU

Вложенные асинхронные вызовы. Объект Deferred в деталях.

Объект Deferred инкапсулирует последовательность обработчиков для еще не существующего результата, чем сильно упрощает сложные AJAX-приложения. Он предоставляется различными фреймворками (Dojo Toolkit, Mochikit) и отдельными библиотечками (jsDeferred, Promises etc).

С его помощью можно добавлять обработчики не в момент запуска метода (например, посылки запроса XMLHTTPRequest() , а в любой момент после этого.

Основные методы объекта Deferred:

  • addCallback(функция-обработчик)
  • addErrback(функция-обработчик)
  • callback(результат)
  • errback(результат)

Обычно, когда функция возвращает Deferred, т.е "обещание результата в некоторый момент", программист затем цепляет к нему обработчики результата, которые будут вызваны в той же последовательности, через addCallback/addErrback.

Код, который управляет объектом Deferred, т.е тот код, который дал обещание предоставить результат, должен вызвать callback() или errback() методы в тот момент, когда результат появится. Например, после завершения некоторой асинхронной операции.

При этом будет вызван первый в цепочке обработчик, добавленный при помощи addCallback() или addErrback() соответственно.

Каждый следующий обработчик получит результат, который вернул предыдущий.

Вот - самый простой пример обработчика:

var deferred = new Deferred()
deferred.addCallback(function(result) { return result })
Важный принцип при работе с Deferred: каждый обработчик должен возвращать результат. Так что затем всегда можно продолжить работу с этим результатом.

Например, вот wrapper для XmlHTTPRequest, который возвращает объект Deferred:

function xhrGet(url) {	
	var deferred = new Deferred()
	var xhr = new XmlHttpRequest()
	xhr.open("GET", url, true); 
	xhr.onreadystatechange = function() {
		if (xhr.readyState!=4) return
		if (xhr.status==200) {
			deferred.callback(xhr.responseText)  
		} else {
			deferred.errback(xhr.statusText)
		}
	}
	xhr.send(null)

	return deferred
}

А внешний код может добавить к нему обработчики для успешно полученного результата и для ошибки:

var deferred = xhrGet("http://someurl")
deferred.addCallback(function(res) { alert("Result: "+res); return res })
deferred.addErrback(function(err) { alert("Error: "+err); return err })

Внутри Deferred - это просто упорядоченный список пар callback/errback. Методы addCallback, addErrback, addBoth и addCallbacks добавляют в него элементы.

Например, последовательность обработчиков

var deferred = new Deferred()
deferred.addCallback(myCallback)
deferred.addErrback(myErrbac)
deferred.addBoth(myBoth)
deferred.addCallbacks(myCallback, myErrback)

внутри объекта Deferred становится списком:

[
	[myCallback, null],
	[null, myErrback],
	[myBoth, myBoth],
	[myCallback, myErrback]
]

Каждый вызов add* добавляет один элемент в список, как показано выше.

Внутри у Deferred есть одно из трех состояний (свойство "fired"):

  • -1, еще нет результата
  • 0, есть результат "success"
  • 1, произошла ошибка "error"

Deferred приходит в состояние "error" в одном из трех случаев:

  1. аргумент callback или errback является instanceof Error
  2. из последнего обработчика выпал exception
  3. последний обработчик вернул значение instanceof Error

Во всех остальных случаях, Deferred находится в состоянии "success".

Состояние Deferred определяет, какой обработчик будет вызван из следующего элемента последовательности. Если соответствующее значение равно null (например, надо вызвать errback, а элемент имеет вид [callback, null]), то этот элемент пропускается.

В случае с обработчиками выше, результат будет обработан примерно так (представьте, что все exceptions перехватываются и возвращаются):

// d.callback(result) or d.errback(result)
if(!(result instanceof Error)){
	result = myCallback(result);
}
if(result instanceof Error){
	result = myErrback(result);
}
result = myBoth(result);
if(result instanceof Error){
	result = myErrback(result);
}else{
	result = myCallback(result);
}

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

Обработчики, в свою очередь, могут возвращать объекты Deferred.

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

Таким способом можно сделать реализовать последовательность вложенных асинхронных вызовов.

При создании объекта Deferred можно задавать "canceller" - функцию, которая будет вызвана, если до появления результата произойдет вызов Deferred.cancel.

С помощью canceller можно реализовать "чистый" обрыв XMLHTTPRequest, и т.п.

Вызов cancel запустит последовательность обработчиков Deferred с ошибкой CancelledError (если canceller не вернет другой результат), поэтому обработчики errback должны быть готовы к такой ошибке, если, конечно, вызов cancel в принципе возможен.

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

Например, вместо того, чтобы регистрировать callback-функцию, которая будет вызвана при окончании операции рендеринга, можно просто вернуть Deferred.

// объявление с callback
function renderLotsOfData(data, callback){
	var success = false
	try{
		for(var x in data){
			renderDataitem(data[x]);
		}
		success = true;
	}catch(e){ }
	if(callback){
		callback(success);
	}
}

// использование объявления с callback
renderLotsOfData(someDataObj, function(success){
	// handles success or failure
	if(!success){
		promptUserToRecover();
	}
})

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

Кроме того, Deferred освобождает от беспокойства на тему "а, может, вызов уже произошел?", например, в случае возврата результата из кеша. С Deferred, новые обработчики могут быть добавлены в любой момент, даже если результат уже получен.

function renderLotsOfData(data){
	var d = new Deferred();
	try{
		for(var x in data){
			renderDataitem(data[x]);
		}
		d.callback(true);
	}catch(e){ 
		d.errback(new Error("rendering failed"));
	}
	return d;
}

// использование Deferred 
renderLotsOfData(someDataObj).addErrback(function(){
	promptUserToRecover();
});
// NOTE: addErrback и addCallback возвращают тот же Deferred,
// так что мы можем тут же добавить в цепочку новые обработчики
// или сохранить deferred для добавления обработчиков позже.

В этом примере renderLotsOfData работает синхронно, так что оба варианта довольно-таки искусственные. Поставим отображение данных в setTimeout (типа идет анимация), чтобы почувствовать, как крут Deferred:

// Deferred и асинхронная функция
function renderLotsOfData(data){
	var d = new Deferred()
	setTimeout(function(){
		try{
			for(var x in data){
				renderDataitem(data[x]);
			}
			d.callback(true);
		}catch(e){ 
			d.errback(new Error("rendering failed"));
		}
	}, 100);
	return d;
}

// используем Deferred для вызова
renderLotsOfData(someDataObj).addErrback(function(){
	promptUserToRecover()
})

// Заметим, что вызывающий код не потребовалось исправлять
// для поддержки асинхронной работы

Благодаря Deferred - почти не пришлось ничего менять, порядок обработчиков соответствует реально происходящему, в общем - все максимально удобно и наглядно!

Объект DeferredList расширяет Deferred.

Он позволяет ставить каллбек сразу на пачку асинхронных вызовов.

Это полезно, например, для асинхронной загрузки всех узлов на одном уровне javascript-дерева, когда хочется сделать что-то после окончания общей загрузки.

Объект Deferred есть в javascript-фреймворках Dojo и Mochikit. Кроме того, и там и там есть вспомогательный объект DeferredList.

Собственно, эта статья написана частично по документации dojo. Автор считает, что имеет на это право, т.к сам приложил руку к этой части разработки данного фреймворка . Впрочем, из обоих фреймворков Deferred можно легко вынуть и приспособить к собственной библиотеке, если таковая у вас имеется.

Успешной асинхронной разработки.


Автор: Гость (не зарегистрирован), дата: 29 августа, 2008 - 15:32
#permalink

Что-то я дочитал до конца и понял, что Deffered только при наличии фреймворков есть. Можно было это в начале написать))


Автор: chebur (не зарегистрирован), дата: 18 февраля, 2009 - 22:46
#permalink

> С помощью canceller можно реализовать "чистый" обрыв XMLHTTPRequest, и т.п.
что такое "чистый обрыв XHR"?
не совсем понятно зачем нужен canceler, можете привести пример?


Автор: Илья Кантор, дата: 16 января, 2011 - 18:32
#permalink

Для отмены любого асинхронного события.


Автор: Гость (не зарегистрирован), дата: 30 марта, 2009 - 12:44
#permalink

Часто необходимо для работы с вводом в Text-box'ы - когда человек нажал 1ую букву пошёл первый запрос, через несколько миллисекунд человек нажал вторую букву - пошёл второй запрос. Если, например на сервере идёт select с условием where text like '<введённые символы>%' то результат с 2ми буквами может вернуть гораздо меньше строк (в разы) да и выполнится быстрее, скорее всего - в результате получается, что ответ от запроса 2 может придти раньше 1-го, а первый, в свою очередь, может "затереть" второй. Для пользователя это будет выглядеть, так, как будто 2ой запрос не выполнился.


Автор: Гость (не зарегистрирован), дата: 13 июня, 2009 - 20:42
#permalink

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


Автор: Гость (не зарегистрирован), дата: 6 июля, 2009 - 17:39
#permalink

В таком случае callback будет вызван сразу же после его добавления к объекту Deferred.


Автор: oleg2, дата: 17 сентября, 2010 - 10:20
#permalink

Если у Deferred вызвать errback с "не ошибкой", например .errback(10) - по описанию получается что Deferred будет в состоянии "success" и вызовы начнутся с callback- в не errback-функций.

Так ли это? Правильно ли это?


Автор: Ilya Kharlamov, дата: 26 марта, 2011 - 01:16
#permalink

С простыми Deferred всё просто и понятно. Но проблемы начинаются когда начинается вложенность и циклы.
К примеру, есть массив id юзеров

var userIds = [3, 18, 10];

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

var groupNames = ['Admin', 'Guest', 'User']

предположим, есть в наличии два XMLHttpRequest : getGroupIdByUserId и getGroupNameByGroupId
Просто, не правда ли? Но попробуйте написать код, который бы это делал асинхронно, используя Deferred и остался читабельным.


Автор: Гость (не зарегистрирован), дата: 29 октября, 2014 - 02:05
#permalink

console.log('hello')


Автор: Sheryl Webb (не зарегистрирован), дата: 18 апреля, 2019 - 09:53
#permalink

Можно конечно использовать существующие библиотеки, но мне нравится этот вариант. happy wheels free play


Автор: ashenshakily (не зарегистрирован), дата: 1 мая, 2019 - 16:41
#permalink

Объект Deferred инкапсулирует последовательность обработчиков для еще не существующего результата, чем сильно упрощает сложные AJAX-приложения. Он предоставляется различными фреймворками (Dojo Toolkit, Mochikit) и отдельными библиотечками (madalin stunt cars 2, Promises etc).


Автор: katedaisy (не зарегистрирован), дата: 14 мая, 2019 - 12:06
#permalink

Thanks for this wonderful article and continue sharing more topics like this.
cool math games


Автор: happywheelspace (не зарегистрирован), дата: 13 июня, 2019 - 15:23
#permalink

Thanks for this all detailed information. Providing all the coding and coding errors and telling us the proper way of doing it, happy wheels unblocked free online.


Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
9 + 2 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние комментарии
Последние темы на форуме
Forum