如何在Javascript中避免意大利面条代码

时间:2009-10-15 15:38:57

标签: javascript ajax

当我不得不处理异步时,我发现自己在Javascript中写了很多意大利面 应用程序(特别是在处理必须获取所有数据的OpenSocial代码时) 通过JS)。通常的模式类似于:

  1. 用户首次登录应用程序,获取他的数据。
  2. 对他的数据执行A操作(例如,通过向服务器发送请求来获取他的朋友)。
  3. 对此数据执行B操作(例如,将他的朋友发送到服务器进行某些处理)。
  4. 对他的数据执行C(例如,检查服务器响应是否有效,以便我们可以执行其他操作)。
  5. 请注意,此顺序执行路径(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();
    }
    

    你可以看到每个函数都依赖于前一个函数,更糟糕的是,它必须被调用 一个有用的特定订单。当你必须添加显示/隐藏加载等内容时会变得更糟 用户等待时屏幕和其他噱头。

    你会如何解决这个问题?

6 个答案:

答案 0 :(得分:4)

一个选项可能是拥有一个显示当前状态的变量,并且具有一个始终是AJAX回调函数的“controller”函数。根据当前状态,控制器功能将在线调用下一个功能。为了简化控制器功能,我可能会存储要在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 
}

答案 1 :(得分:2)

你可以用Observer pattern之类的东西控制这种意大利面条。一些JavaScript框架具有此功能的即用型实现,例如Dojo's发布/订阅功能。

答案 2 :(得分:1)

使用MVC架构构建您的javascript客户端

答案 3 :(得分:1)

应该将处理显示,隐藏和状态功能的代码提取到函数中。然后,为了避免“spaghettiness”,一种解决方案是使用内联的匿名函数。

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();
    });
  });
}

答案 4 :(得分:1)

部分问题在于您将此视为一个四步过程,需要三次往返服务器。如果你真的认为这是一个单一的工作流程,以及用户最有可能做的事情,那么最好的加速是在第一次交互中收集尽可能多的信息以减少往返。这可能包括允许用户选中一个表示她想要遵循此路径的框,这样您就不必在两个步骤之间返回用户,或者允许她输入您可以处理的朋友姓名的猜测第一次,或第一次预加载名单。

如果这只是用户可能遵循的众多路径之一,那么您概述代码的方式效果最佳;需要进行多次往返,因为在每次互动中,您都会发现用户想要的内容,而另一个答案则会向您发送不同的方向。这就是你贬低松散耦合的代码风格真的很闪耀的时候。看起来每个步骤都与之前的步骤断开,因为用户的操作正在推动活动。

因此,真正的问题是用户是否在开始时有一个选择来确定代码的方向。如果没有,那么您希望优化您知道(或强烈预测)交互的路径。另一方面,如果用户交互推动了这个过程,那么解耦步骤并对每个交互作出反应是正确的,但是你会期待更多不同的可能性。

答案 5 :(得分:0)

如果您希望您的函数能够独立运行,请为异步函数配备通用回调,而不是调用“next”函数。然后,正如JacobM所说,设置一个“控制器”,将按顺序调用它们。我已经修改了下面的示例代码来演示(请注意,这尚未经过测试):

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/。如果你想变得更复杂(再次,正如雅各布建议的那样),你可以编写一些自动处理它的代码 - 给它一个函数列表,然后按顺序执行它们,传递回调数据。方便,但可能会满足您的需求。