1) Что такое «ад обратных вызовов» для тех, кто не знает javascript и node.js?
В этом другом вопросе есть несколько примеров ада обратного вызова Javascript: как избежать длительного вложения асинхронных функций в Node.js
Проблема в Javascript заключается в том, что единственный способ «заморозить» вычисление и заставить «остальную часть» выполнить последнюю (асинхронно) - это поместить «остальную часть» в обратный вызов.
Например, скажем, я хочу запустить код, который выглядит так:
x = getData();
y = getMoreData(x);
z = getMoreData(y);
...
Что произойдет, если теперь я хочу сделать функции getData асинхронными, что означает, что у меня есть возможность запустить другой код, пока я жду, пока они вернут свои значения? В Javascript единственный способ - переписать все, что касается асинхронного вычисления, используя стиль передачи продолжения :
getData(function(x){
getMoreData(x, function(y){
getMoreData(y, function(z){
...
});
});
});
Не думаю, что мне нужно кого-то убеждать в том, что эта версия уродливее предыдущей. :-)
2) Когда (в каких настройках) возникает "проблема с обратным вызовом"?
Когда в вашем коде много функций обратного вызова! Чем больше их в вашем коде, тем труднее с ними работать, и становится особенно плохо, когда вам нужно делать циклы, блоки try-catch и тому подобное.
Например, насколько мне известно, в JavaScript единственный способ выполнить ряд асинхронных функций, одна из которых запускается после предыдущих возвратов, - это использование рекурсивной функции. Вы не можете использовать цикл for.
// we would like to write the following
for(var i=0; i<10; i++){
doSomething(i);
}
blah();
Вместо этого нам, возможно, придется написать:
function loop(i, onDone){
if(i >= 10){
onDone()
}else{
doSomething(i, function(){
loop(i+1, onDone);
});
}
}
loop(0, function(){
blah();
});
//ugh!
Количество вопросов, которые мы получаем здесь, в StackOverflow, спрашивая, как это сделать, является свидетельством того, насколько это запутанно :)
3) Почему это происходит?
Это происходит потому, что в JavaScript единственный способ отложить вычисление, чтобы оно выполнялось после возврата асинхронного вызова, - это поместить отложенный код в функцию обратного вызова. Вы не можете откладывать код, который был написан в традиционном синхронном стиле, поэтому вы повсюду получаете вложенные обратные вызовы.
4) Или «ад обратного вызова» может происходить и в однопоточном приложении?
Асинхронное программирование связано с параллелизмом, а однопоточное - с параллелизмом. Эти две концепции на самом деле не одно и то же.
Вы все еще можете иметь параллельный код в однопоточном контексте. Фактически, JavaScript, королева ада обратных вызовов, является однопоточным.
В чем разница между параллелизмом и параллелизмом?
5) Не могли бы вы также показать, как RX решает «проблему ада обратного вызова» на этом простом примере.
Я ничего не знаю о RX в частности, но обычно эта проблема решается путем добавления встроенной поддержки асинхронных вычислений в языке программирования. Реализации могут различаться и включать: async, генераторы, сопрограммы и callcc.
В Python мы можем реализовать этот предыдущий пример цикла с помощью чего-то вроде:
def myLoop():
for i in range(10):
doSomething(i)
yield
myGen = myLoop()
Это не полный код, но идея состоит в том, что "yield" приостанавливает наш цикл for, пока кто-нибудь не вызовет myGen.next (). Важно то, что мы все еще могли писать код, используя цикл for, без необходимости выворачивать логику «наизнанку», как мы должны были сделать в этой рекурсивной loop
функции.
Просто ответьте на вопрос: не могли бы вы также показать, как RX решает «адскую проблему обратного вызова» на этом простом примере?
Магия есть
flatMap
. Мы можем написать следующий код в Rx для примера @hugomg:Это как если бы вы пишете несколько синхронных кодов FP, но на самом деле вы можете сделать их асинхронными с помощью
Scheduler
.источник
Чтобы ответить на вопрос, как Rx решает ад обратных вызовов :
Сначала давайте снова опишем ад обратных вызовов.
Представьте себе случай, когда мы должны выполнить http, чтобы получить три ресурса - человека, планету и галактику. Наша цель - найти галактику, в которой живет человек. Сначала мы должны найти человека, затем планету, а затем галактику. Это три обратных вызова для трех асинхронных операций.
Каждый обратный вызов является вложенным. Каждый внутренний обратный вызов зависит от своего родителя. Это приводит к аду обратного вызова в стиле «пирамиды гибели» . Код выглядит как знак>.
Чтобы решить эту проблему в RxJs, вы можете сделать что-то вроде этого:
С помощью оператора
mergeMap
AKAflatMap
вы можете сделать его более лаконичным:Как видите, код сплющен и содержит единую цепочку вызовов методов. У нас нет «пирамиды гибели».
Следовательно, можно избежать ада обратного вызова.
Если вам интересно, обещания - это еще один способ избежать ада обратных вызовов, но обещания нетерпеливы , а не ленивы, как наблюдаемые, и (вообще говоря) вы не можете их так легко отменить.
источник
Ад обратного вызова - это любой код, в котором использование обратных вызовов функций в асинхронном коде становится неясным или трудным для понимания. Как правило, когда существует более одного уровня косвенного обращения, код, использующий обратные вызовы, может стать труднее отслеживать, труднее рефакторинг и труднее тестировать. Запах кода - это несколько уровней отступов из-за передачи нескольких уровней функциональных литералов.
Это часто случается, когда поведение имеет зависимости, то есть когда A должно произойти до того, как B должно произойти до C. Тогда вы получите такой код:
Если в вашем коде много таких поведенческих зависимостей, это может быстро стать проблемой. Особенно если ветки ...
Так не пойдет. Как мы можем заставить асинхронный код выполняться в определенном порядке без необходимости передавать все эти обратные вызовы?
RX - это сокращение от «реактивные расширения». Я не использовал его, но Google предлагает, чтобы это основанная на событиях структура, что имеет смысл. События - это общий шаблон, заставляющий код выполняться по порядку без создания хрупкой связи . Вы можете заставить C прослушивать событие 'bFinished', которое происходит только после того, как B вызывается для прослушивания 'aFinished'. Затем вы можете легко добавить дополнительные шаги или расширить такое поведение и легко проверить , выполняется ли ваш код по порядку, просто транслируя события в вашем тестовом примере.
источник
Ад обратного вызова означает, что вы находитесь внутри обратного вызова внутри другого обратного вызова, и он переходит к n-му вызову, пока ваши потребности не будут удовлетворены.
Давайте разберемся на примере поддельного вызова ajax с использованием API установки тайм-аута, допустим, у нас есть API рецептов, нам нужно загрузить все рецепты.
В приведенном выше примере через 1,5 секунды, когда истечет время таймера, будет выполнен внутренний код обратного вызова, другими словами, через наш поддельный вызов ajax все рецепты будут загружены с сервера. Теперь нам нужно загрузить данные конкретного рецепта.
Чтобы загрузить данные конкретного рецепта, мы написали код внутри нашего первого обратного вызова и передали идентификатор рецепта.
Теперь предположим, что нам нужно загрузить все рецепты того же издателя рецепта с идентификатором 7638.
Чтобы полностью удовлетворить нашу потребность, а именно загрузить все рецепты имени издателя suru, мы написали код внутри нашего второго обратного вызова. Понятно, что мы написали цепочку обратных вызовов, которая называется адом обратных вызовов.
Если вы хотите избежать ада обратных вызовов, вы можете использовать Promise, который является функцией js es6, каждое обещание принимает обратный вызов, который вызывается, когда обещание полностью заполнено. Обратный вызов обещания имеет два варианта: разрешен или отклонен. Предположим, что ваш вызов API прошел успешно, вы можете вызвать метод решения и передать данные через разрешение , вы можете получить эти данные с помощью then () . Но если ваш API не работает, вы можете использовать reject, использовать catch, чтобы поймать ошибку. Помните, что обещание всегда используйте тогда для решения и ловите для отклонения
Давайте решим предыдущую адскую проблему обратного вызова с помощью обещания.
Теперь загрузите конкретный рецепт:
Теперь мы можем написать другой вызов метода allRecipeOfAPublisher, например getRecipe, который также будет возвращать обещание, и мы можем написать еще один метод then (), чтобы получить обещание разрешения для allRecipeOfAPublisher, я надеюсь, что на этом этапе вы сможете сделать это самостоятельно.
Итак, мы узнали, как создавать и использовать обещания, а теперь давайте упростим использование обещаний с помощью async / await, представленного в es8.
В приведенном выше примере мы использовали асинхронную функцию, потому что она будет работать в фоновом режиме, внутри асинхронной функции мы использовали ключевое слово await перед каждым методом, который возвращает или является обещанием, потому что ждать в этой позиции, пока это обещание не будет выполнено, другими словами в приведенные ниже коды до тех пор, пока getIds не завершится, или программа отклонения не перестанет выполнять коды, указанные ниже в этой строке, когда будут возвращены идентификаторы, затем мы снова вызвали функцию getRecipe () с идентификатором и подождали, используя ключевое слово await, пока не вернутся данные. Вот как мы наконец оправились от ада обратных вызовов.
Чтобы использовать ожидание, нам понадобится асинхронная функция, мы можем вернуть обещание, поэтому используйте then для обещания разрешения и cath для отклонения обещания
из приведенного выше примера:
источник
Один из способов избежать ада обратного вызова - использовать FRP, который является «улучшенной версией» RX.
Я начал использовать FRP недавно, потому что нашел хорошую его реализацию под названием
Sodium
( http://sodium.nz/ ).Типичный код выглядит так (Scala.js):
selectedNote.updates()
- это a,Stream
которое срабатывает, еслиselectedNode
(а именноCell
) изменяется,NodeEditorWidget
then обновляется соответственно.Таким образом, в зависимости от содержимого
selectedNode
Cell
, редактируемый в данный момент файлNote
.Этот код полностью избегает Callback-ов, почти Cacllback-ы выталкиваются на «внешний уровень» / «поверхность» приложения, где логика обработки состояния взаимодействует с внешним миром. Для распространения данных внутри логики обработки внутреннего состояния (которая реализует конечный автомат) не требуются обратные вызовы.
Полный исходный код здесь
Приведенный выше фрагмент кода соответствует следующему простому примеру создания / отображения / обновления:
Этот код также отправляет обновления на сервер, поэтому изменения в обновленных объектах автоматически сохраняются на сервере.
Вся обработка событий осуществляется с помощью
Stream
s иCell
s. Это концепции FRP. Обратные вызовы необходимы только там, где логика FRP взаимодействует с внешним миром, например, ввод пользователем, редактирование текста, нажатие кнопки, возврат вызова AJAX.Поток данных описывается явно, декларативно с использованием FRP (реализованного библиотекой Sodium), поэтому для описания потока данных не требуется никакой логики обработки событий / обратного вызова.
FRP (который является более «строгой» версией RX) - это способ описания графа потока данных, который может содержать узлы, содержащие состояние. События запускают изменения состояния в состоянии, содержащем узлы (называемые
Cell
s).Sodium - это библиотека FRP более высокого порядка, что означает, что с помощью примитива
flatMap
/switch
можно изменить порядок графа потока данных во время выполнения.Я рекомендую заглянуть в книгу Sodium , в ней подробно объясняется, как FRP избавляется от всех обратных вызовов, которые не являются существенными для описания логики потока данных, которая связана с обновлением состояния приложений в ответ на некоторые внешние стимулы.
При использовании FRP необходимо сохранять только те обратные вызовы, которые описывают взаимодействие с внешним миром. Другими словами, поток данных описывается функционально / декларативно, когда используется структура FRP (например, Sodium) или когда используется структура, подобная FRP (например, RX).
Sodium также доступен для Javascript / Typescript.
источник
Если у вас нет знаний о обратном вызове и обратном вызове ада, нет проблем. Дело в том, что обратный вызов и обратный вызов ада. Например: обратный вызов ада похож на то, что мы можем сохранить класс внутри класса. Как вы слышали о том, что вложенный в C, язык C ++. Вложенный Означает, что класс внутри другого класса.
источник
Используйте jazz.js https://github.com/Javanile/Jazz.js
это упростить так:
источник