Подтвердить что ты не робот

Что такое "ад обратного вызова" и как и почему RX решает его?

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

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

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

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

Или может ли "callback hell" встречаться также в однопоточном приложении?

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

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

4b9b3361

Ответ 1

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, но обычно эта проблема решается путем добавления встроенной поддержки асинхронных вычислений на языке программирования. Реализации могут различаться и включать в себя: асинхронность, генераторы, сопрограммы и callcc.

В Python мы можем реализовать этот предыдущий пример цикла с чем-то вроде:

def myLoop():
    for i in range(10):
        doSomething(i)
        yield

myGen = myLoop()

Это не полный код, но идея в том, что "yield" приостанавливает цикл for, пока кто-то не вызовет myGen.next(). Важно то, что мы могли бы по-прежнему писать код, используя цикл for, без необходимости извлекать логику "наизнанку", как это мы делали в этой рекурсивной функции loop.

Ответ 2

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

Магия flatMap. Мы можем написать следующий код в Rx для примера @hugomg:

def getData() = Observable[X]
getData().flatMap(x -> Observable[Y])
         .flatMap(y -> Observable[Z])
         .map(z -> ...)...

Это похоже на то, что вы пишете некоторые синхронные коды FP, но на самом деле вы можете сделать их асинхронными с помощью Scheduler.

Ответ 3

Чтобы решить вопрос о том, как 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 оператора mergeMap AKA flatMap вы можете сделать его более кратким:

getPerson()
  .mergeMap(person => getPlanet(person))
  .mergeMap(planet => getGalaxy(planet))
  .subscribe(galaxy => console.log(galaxy));

Как видите, код выровнен и содержит одну цепочку вызовов методов. У нас нет "пирамиды гибели".

Следовательно, обратного вызова ада избегают.

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

Ответ 4

Адвокат обратного вызова - это любой код, в котором использование обратных вызовов функций в асинхронном коде становится неясным или трудным для подражания. Как правило, когда имеется более одного уровня косвенности, код с использованием обратных вызовов может стать труднее следовать, сложнее рефакторировать и сложнее проверить. Запах кода - это несколько уровней отступа из-за прохождения нескольких слоев литералов функций.

Это часто происходит, когда поведение имеет зависимости, т.е. когда 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". Затем вы можете легко добавить дополнительные шаги или расширить этот тип поведения и можете легко протестировать, что ваш код выполняется в порядке, просто транслируя события в вашем тестовом случае.

Ответ 5

Если у вас нет знаний об обратном вызове и обратном вызове ада, проблем нет. Дело в том, что обратный вызов и обратный вызов ада. Например: обратный вызов ада похож на то, что мы можем хранить класс внутри класса. Как вы слышали, о том, что вложено в C, C++ language.Nested Означает, что класс внутри другого класса.

Ответ 6

Использовать jazz.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);
      },
    ]);