При выполнении однопоточного асинхронного программирования я знаком с двумя основными приемами. Наиболее распространенным является использование обратных вызовов. Это означает передачу функции, которая асинхронно действует как функция обратного вызова в качестве параметра. Когда асинхронная операция завершится, будет вызван обратный вызов.
Некоторый типичный jQuery
код, разработанный таким образом:
$.get('userDetails', {'name': 'joe'}, function(data) {
$('#userAge').text(data.age);
});
Однако этот тип кода может стать грязным и сильно вложенным, когда мы хотим сделать дополнительные асинхронные вызовы один за другим, когда завершится предыдущий.
Итак, второй подход - использование обещаний. Обещание - это объект, представляющий значение, которое может еще не существовать. Вы можете установить обратные вызовы для него, которые будут вызываться, когда значение будет готово для чтения.
Разница между Promises и традиционным подходом к обратным вызовам заключается в том, что асинхронные методы теперь синхронно возвращают объекты Promise, для которых клиент устанавливает обратный вызов. Например, аналогичный код с использованием Promises в AngularJS:
$http.get('userDetails', {'name': 'joe'})
.then(function(response) {
$('#userAge').text(response.age);
});
Итак, мой вопрос: есть ли на самом деле реальная разница? Разница кажется чисто синтаксической.
Есть ли более глубокая причина использовать одну технику над другой?
Ответы:
Справедливо сказать, что обещания - это просто синтаксический сахар. Все, что вы можете сделать с обещаниями, вы можете сделать с обратными вызовами. На самом деле, большинство реализаций обещаний предоставляют способы преобразования между ними в любое время.
Глубокая причина, почему обещания часто лучше, состоит в том, что они более сочетаемы , что примерно означает, что объединение нескольких обещаний «просто работает», а объединение нескольких обратных вызовов часто - нет. Например, тривиально назначить обещание переменной и позже присоединить к нему дополнительные обработчики или даже присоединить обработчик к большой группе обещаний, которая выполняется только после разрешения всех обещаний. Хотя вы можете эмулировать эти вещи с помощью обратных вызовов, для этого требуется гораздо больше кода, его очень сложно сделать правильно, а конечный результат, как правило, гораздо менее обслуживаем.
Один из самых больших (и тончайших) способов получения обещаний их компоновки - это единообразная обработка возвращаемых значений и необработанных исключений. В случае обратных вызовов способ обработки исключения может полностью зависеть от того, какой из множества вложенных обратных вызовов его выбросил, и какая из функций, принимающих обратные вызовы, имеет реализацию try / catch. С обещаниями вы знаете, что исключение, которое выходит за пределы одной функции обратного вызова, будет перехвачено и передано в обработчик ошибок, предоставленный вами
.error()
или.catch()
.Для примера, который вы привели с одним обратным вызовом против одного обещания, это правда, что нет существенной разницы. Когда у вас есть миллионы обратных вызовов против тысячи обещаний, код, основанный на обещаниях, имеет тенденцию выглядеть намного лучше.
Вот попытка некоторого гипотетического кода, написанного с обещаниями, а затем с обратными вызовами, которые должны быть достаточно сложными, чтобы дать вам некоторое представление о том, о чем я говорю.
С обещаниями:
С обратными вызовами:
Могут быть некоторые умные способы уменьшить дублирование кода в версии обратных вызовов даже без обещаний, но все, что я могу придумать, сводятся к реализации чего-то очень похожего на обещание.
источник
yield
обещаний ed. Преимущество здесь в том, что вы получаете возможность смешивать в собственных структурах потока управления, которые могут варьироваться в зависимости от того, сколько асинхронных операций они выполняют. Я добавлю версию, которая показывает это.then(callback)
метода в Promise, который принимает обратный вызов (вместо метода в API, принимающего этот обратный вызов), не должен ничего делать с IoC. Promise вводит один уровень косвенности, который полезен для компоновки, объединения в цепочку и обработки ошибок (в действительности это ориентированное на железную дорогу программирование), но клиент по-прежнему не выполняет обратный вызов, поэтому на самом деле отсутствие IoC.