Как избежать спагетти кода в 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 функции публикации/подписки.
Ответ 3
Создайте свой javascript-клиент с архитектурой MVC
Ответ 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), вы можете написать код, который обрабатывает это автоматически, - дать ему список функций, и он выполняет их по порядку, передавая данные обратного вызова. Удобно, но может быть излишним для ваших нужд.