Что такое "callback hell" и как и почему RX решает его?


может ли кто-нибудь дать четкое определение вместе с простым примером, который объясняет, что такое "обратный вызов ада" для тех, кто не знает JavaScript и узел.Джей ?

когда (в каких настройках) возникает "проблема обратного вызова ада"?

Почему это происходит?

всегда ли" обратный вызов hell " связан с асинхронными вычислениями?

или может "callback hell" произойти также в однопоточном приложении?

Я взял Реактивный курс в Coursera и Эрик Мейер сказал в одной из своих лекций, что RX решает проблему "обратного вызова ада". Я спросил, Что такое" обратный вызов ада " на форуме Coursera, но я не получил четкого ответа.

после объяснения "callback hell "на простом примере, не могли бы вы также показать, как RX решает" callback hell problem " на этом простом примере?

5 83

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);
      },
    ]);