Как избежать спагетти кода в Javascript

Я нахожу, что пишу много спагетти в Javascript, когда мне приходится иметь дело с асинхронными приложений (особенно при работе с кодом OpenSocial, где все данные должны быть получены через JS). Обычный шаблон выглядит примерно так:

  • Пользователь регистрируется в приложении в первый раз, получает свои данные.
  • Сделайте A на своих данных (например, получите друзей, отправив запрос на сервер).
  • Сделайте B по этим данным (например, отправьте своих друзей на сервер для некоторой обработки).
  • Сделайте C на своих данных (например, проверьте, что ответ сервера действителен, поэтому мы можем сделать что-то еще).

Обратите внимание, что этот последовательный путь выполнения (1 = > 2 = > 3 = > 4) не соответствует хорошо асинхронному. природа Ajax, поэтому пользователь заканчивает ожидание долгое время, и код превращается в беспорядок с каждого шага зависит от предыдущих.

Пример с кодом:

gadgets.util.registerOnLoadHandler(setupUser())
...
function setupUser() {
  var req = [get data and setup request]
  req.send(some_url, some_data, function(response) { getFriendsFor(response.user) });
}

function getFriendsFor(user) {
  var friends = [get friends from user]
  var req = [setup request] 
  req.send(some_other_url, some_other_data, function(response { validateFriendsResponse(response.friends) });
}

function validateFriendsResponse(friends) {
  if (friends.valid())
    ...
  loadCanvas();
}

Вы можете видеть, что каждая функция зависит от предыдущей, и что еще хуже, ее нужно вызвать в конкретный порядок, который будет полезен. Ухудшается, когда вам приходится добавлять такие вещи, как показ/скрытие загрузки экраны и другие трюки, пока пользователь ждет.

Как вы можете это исправить?

Ответы

Ответ 1

Один вариант может состоять в том, чтобы иметь переменную, которая показывает текущее состояние, и иметь функцию "контроллер", которая всегда является функцией обратного вызова AJAX. На основе текущего состояния функция контроллера вызовет следующую функцию в строке. Чтобы упростить функцию контроллера, я бы, вероятно, сохранил последовательность функций для вызова объекта Javascript, поэтому вся функция контроллера выполняет поиск и переходит к следующей функции в последовательности. Этот подход может быть облегчен за счет наличия одного объекта Javascript, который всегда является параметром функции (и содержит все данные, которые были возвращены ранее вызовами AJAX.

Пример:

var user = {};
var currentState = 1;

var someFunction = function(user) {//stuff here that adds data to user via AJAX, advances currentState, and calls controllerFunction as callback};
var someOtherFunction = function(user) {//stuff here that does other things to user, advances currentState, and calls controllerFunction as callback}

var functionSequence = {1:someFunction, 2:someOtherFunction}

var controllerFunction = function() {
   //retrieve function from functionSequence based on current state, and call it with user as parameter 
}

Ответ 2

Вы можете управлять подобными спагетти такими вещами, как шаблон наблюдателя. Некоторые фреймворки JavaScript имеют готовую к использованию реализацию этой функции, например Dojo 's функции публикации/подписки.

Ответ 4

Код для обработки функций отображения, скрытия и состояния должен быть извлечен в функции. Затем, чтобы избежать "спагесткости", одним из решений является использование анонимных функций inline.

function setupUser() {
  var req = [get data and setup request]
  req.send(some_url, some_data, function(response) { 
    var friends = [get friends from user]
    var req = [setup request] 
    req.send(some_other_url, some_other_data, function(response {         
      if (friends.valid())
      ...
      loadCanvas();
    });
  });
}

Ответ 5

Частично проблема заключается в том, что вы думаете об этом как о четырехэтапном процессе с тремя раундами на сервер. Если вы действительно думаете, что это единственный рабочий процесс и то, что пользователь, скорее всего, сделает, то лучшим ускорением является сбор как можно большего количества информации в первом взаимодействии, чтобы уменьшить круговые поездки. Это может включать в себя разрешение пользователю проверять поле, в котором говорится, что она хочет следовать этому пути, поэтому вам не нужно возвращаться к пользователю между шагами или позволять ей вводить догадки именам друзей, которые вы можете обрабатывать первый раз или предварительно загрузить список имен в первый раз.

То, как вы наметили код, работает лучше всего, если это всего лишь один из многих путей, которым может следовать пользователь; требуется многократная поездка, потому что при каждом взаимодействии вы узнаете, чего хочет пользователь, а другой ответ отправил бы вас в другом направлении. Это когда хорошо сочетается стиль кода, который вы пренебрежительно освещаете. Похоже, что каждый шаг отключен от того, что было раньше, потому что действия пользователя приводят в действие.

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

Ответ 6

Если вы хотите, чтобы ваши функции могли работать независимо, оснастите асинхронные вызовы с помощью общих обратных вызовов вместо вызовов на "следующую" функцию в строке. Затем, как сказал ЯкобМ, настройте "контроллер", который будет вызывать их последовательно. Я изменил ваш пример кода ниже, чтобы продемонстрировать (будьте осторожны, это не было протестировано):

gadgets.util.registerOnLoadHandler(userSetupController())
...
function setupUser(callback) {
  var req = [get data and setup request]
  req.send(some_url, some_data, function(response) { callback(response.user) });
}

function getFriendsFor(user,callback) {
  var friends = [get friends from user]
  var req = [setup request] 
  req.send(some_other_url, some_other_data, function(response { callback(response.friends) });
}

function validateFriendsResponse(friends) {
  if (friends.valid())
    return true;
  else
    return false;
}

function userSetupController() {
    setupUser(function(user){
        getFriendsFor(user,function(friends){
            if (validateFriendsResponse(friends)) {
                loadCanvas();
            } else {
                // don't load the canvas?
            }
        });
    });
}

Создание обратных вызовов становится немного сложным, если вы не знакомы с ними - вот достойное объяснение: http://pixelpushing.net/2009/04/anonymous-function-callbacks/. Если вы хотите усложниться (опять же, как предположил JacobM), вы можете написать код, который обрабатывает это автоматически, - дать ему список функций, и он выполняет их по порядку, передавая данные обратного вызова. Удобно, но может быть излишним для ваших нужд.