Что такое "callback hell" и как и почему RX решает его?
может ли кто-нибудь дать четкое определение вместе с простым примером, который объясняет, что такое "обратный вызов ада" для тех, кто не знает JavaScript и узел.Джей ?
когда (в каких настройках) возникает "проблема обратного вызова ада"?
Почему это происходит?
всегда ли" обратный вызов hell " связан с асинхронными вычислениями?
или может "callback hell" произойти также в однопоточном приложении?
Я взял Реактивный курс в Coursera и Эрик Мейер сказал в одной из своих лекций, что RX решает проблему "обратного вызова ада". Я спросил, Что такое" обратный вызов ада " на форуме Coursera, но я не получил четкого ответа.
после объяснения "callback hell "на простом примере, не могли бы вы также показать, как RX решает" callback hell problem " на этом простом примере?
5 ответов:
1) Что такое "обратный вызов ад" для тех, кто не знает javascript и узел.Джей ?
этот другой вопрос имеет некоторые примеры Javascript callback hell:как избежать длительного вложения асинхронных функций в узел.js
проблема в Javascript заключается в том, что единственный способ "заморозить" вычисление и заставить "остальную часть" выполнить последнее (асинхронно) - это поместить "остальную часть" внутри обратного вызова.
например, скажем, я хочу запустить код, который выглядит так:
x = getData(); y = getMoreData(x); z = getMoreData(y); ...
что произойдет, если теперь я хочу сделать функции getData асинхронными, что означает, что я получаю возможность запустить какой-то другой код, пока я жду, пока они вернут свои значения? В Javascript единственным способом было бы переписать все, что касается асинхронного вычисления, используя продолжение прохождения стиле:
getData(function(x){ getMoreData(x, function(y){ getMoreData(y, function(z){ ... }); }); });
Я не думаю, что мне нужно убеждать кого-либо, что эта версия уродливее, чем предыдущая. : -)
2) Когда (в каких настройках) возникает "проблема обратного вызова ада"?
когда у вас есть много функций обратного вызова в коде! Становится все труднее работать с ними, чем больше их у вас в коде, и это становится особенно плохо, когда вам нужно делать циклы, пытаться поймать блоки и тому подобное.
например, насколько я знаю, в JavaScript единственный способ выполнить ряд асинхронных функций, где один запускается после предыдущего возвращает использует рекурсивную функцию. Вы не можете использовать цикл for.
//we would like to write 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) или может "callback hell" произойти также в однопоточном приложении?
асинхронное программирование связано с параллелизмом, а однопоточное-с параллелизмом. Эти два понятия на самом деле не одно и то же.
вы все еще можете иметь параллельный код в однопоточном контексте. Фактически, JavaScript, королева обратного вызова ада,является однопоточным.
в чем разница между параллелизмом и параллелизмом?
5) не могли бы вы также показать, как RX решает "проблему обратного вызова ада" на этом простом примере.
Я ничего не знаю о RX в частности, но обычно эта проблема решается путем добавления встроенной поддержки асинхронных вычислений на языке программирования. Это может очень много и может прийти в различные имена (асинхронные, генераторы, сопрограммы, callcc, ...). Например, в Python мы можем реализовать этот предыдущий пример цикла с чем-то вроде:
def myLoop(): for i in range(10): doSomething(i) yield myGen = myLoop()
это не полный код, но идея заключается в том, что" выход " приостанавливает наш цикл for, пока кто-то не вызовет myGen.следующий.)( Важно то, что мы все еще можем написать код, используя цикл for, без необходимости выворачивать логику "наизнанку", как мы должны были сделать в этом рекурсивном
просто ответьте на вопрос: не могли бы вы также показать, как RX решает "проблему обратного вызова ада" на этом простом примере?
магия
flatMap
. Мы можем написать следующий код в Rx для примера @hugomg:def getData() = Observable[X] getData().flatMap(x -> Observable[Y]) .flatMap(y -> Observable[Z]) .map(z -> ...)...
Это похоже на то, что вы пишете некоторые синхронные коды FP, но на самом деле вы можете сделать их асинхронными
Scheduler
.
Callback hell-это любой код, в котором использование обратных вызовов функций в асинхронном коде становится неясным или трудно следовать. Как правило, когда существует более одного уровня косвенности, код с использованием обратных вызовов может стать сложнее следовать, сложнее рефакторинг и сложнее тестировать. Запах кода-это несколько уровней отступа из-за передачи нескольких слоев литералов функций.
это часто происходит, когда поведение имеет зависимости, т. е. когда A должно произойти до B должно произойти раньше C. Затем вы получаете такой код:
a({ parameter : someParameter, callback : function() { b({ parameter : someOtherParameter, callback : function({ c(yetAnotherParameter) }) } });
Если у вас есть много поведенческих зависимостей кода, это может быть хлопотно быстро. Особенно если он ветвится...
a({ parameter : someParameter, callback : function(status) { if (status == states.SUCCESS) { b(function(status) { if (status == states.SUCCESS) { c(function(status){ if (status == states.SUCCESS) { // Not an exaggeration. I have seen // code that looks like this regularly. } }); } }); } elseif (status == states.PENDING { ... } } });
Так не пойдет. Как мы можем заставить асинхронный код выполняться в определенном порядке без необходимости передавать все эти обратные вызовы?
RX-это сокращение от "реактивных расширений". Я не использовал его, но Googling предполагает, что это структура, основанная на событиях, что имеет смысл. события являются общим шаблоном для выполнения кода по порядку без создания хрупкой связи. Вы можете заставить C слушать событие "bFinished", которое происходит только после того, как B называется прослушиванием "aFinished". Затем вы можете легко добавить дополнительные шаги или расширить этот вид поведения, и может легко проверить что ваш код выполняется по порядку, просто транслируя события в вашем тестовом случае.
для решения вопроса о том, как Rx решает обратного вызова ад:
сначала давайте снова опишем ад обратного вызова.
представьте себе случай, когда мы должны сделать http, чтобы получить три ресурса - человек, планета и галактика. Наша цель-найти галактику, в которой живет человек. Сначала мы должны получить человека, затем планету, затем галактику. Это три обратных вызова для трех асинхронных операций.
getPerson(person => { getPlanet(person, (planet) => { getGalaxy(planet, (galaxy) => { console.log(galaxy); }); }); });
каждый обратный вызов является вложенным. Каждый внутренний обратный вызов зависит от своего родителя. Это приводит к" пирамиде судьбы " стиль обратного вызова ад. Код выглядит как знак+.
чтобы решить эту проблему в RxJs вы могли бы сделать что-то вроде так:
getPerson() .map(person => getPlanet(person)) .map(planet => getGalaxy(planet)) .mergeAll() .subscribe(galaxy => console.log(galaxy));
С
mergeMap
АКАflatMap
оператор вы могли бы сделать его более кратким:getPerson() .mergeMap(person => getPlanet(person)) .mergeMap(planet => getGalaxy(planet)) .subscribe(galaxy => console.log(galaxy));
как вы можете видеть, код сглажен и содержит одну цепочку вызовов методов. У нас нет никакой"пирамиды обреченности".
следовательно, обратный вызов ад избежавший.
если вам интересно,обещания другой способ избежать обратного вызова ад, но обещания готовностью, а не лень как наблюдаемых, так и (вообще говоря) вы не можете отменить их так же легко.
использовать джаз.js https://github.com/Javanile/Jazz.js
Это упростить так:
// run sequential task chained jj.script([ // first task function(next) { // at end of this process 'next' point to second task and run it callAsyncProcess1(next); }, // second task function(next) { // at end of this process 'next' point to thirt task and run it callAsyncProcess2(next); }, // thirt task function(next) { // at end of this process 'next' point to (if have) callAsyncProcess3(next); }, ]);