5

除了相对微不足道的功能之外,我倾向于害怕编写 Javascript 的原因之一是,当一件事真正依赖于另一件事时,我从未找到一种避免回调瀑布的体面方法。有没有这样的方法?

我现在正在开发一个 Titanium 应用程序,并遇到了这个现实世界的场景:

我有一组设施,我需要计算与用户当前位置的距离。这需要获取用户的当前位置(只需要发生一次),并且在遍历设施位置时,获取每个位置的位置并计算距离。检索位置(long/lat)的 API 是异步的,因此“简单”的方法如下所示(伪代码如下):

foreach facility {
  API.getCurrentLocation( function( location ) { // async, takes a callback fxn arg
    var here = location.coordinates;

    API.getFacilityLocation( function( e ) { // async, takes a callback fxn arg
      var there    = e. coordinates;
      var distance = API.calculateFrom( here, there );
    });
  });
}

不过,因为这一切都在一个循环中,所以我每次都在计算我的当前位置——比我真正需要做的工作要多。我还没有设法以这样一种方式重构它,即我只获得一次当前位置,并且仍然有该位置可用于距离计算。

鉴于支持 lambda 和闭包的语言的爆炸式增长,我一直认为一定有人找到了一种方法来保持这些瀑布的可管理性,但我还没有找到一个很好的解释来解释如何组织这样的解决方案。

有什么建议或提示吗?

任何见解将不胜感激。

4

4 回答 4

4

基本策略:回调不要使用匿名函数,将循环移动到当前位置返回时运行的回调中。

例子:

function recieveCurrentLocation(location) { // async, takes a callback fxn arg
    var here = location.coordinates;

    // Have to define this callback in the inner scope so it can close
    // over the 'here' value
    function recieveFacilityLocation(e) {
        var there    = e. coordinates;
        var distance = API.calculateFrom( here, there );
    }

    foreach facility {
        API.getFacilityLocation(recieveFacilityLocation);
    }
}

API.getCurrentLocation(recieveCurrentLocation);
于 2011-11-15T14:00:11.733 回答
3

你必须开始思考更多的事件导向。为每个回调级别定义函数并在需要时将其作为参数提供,不要将其视为回调瀑布。请注意,您在每个非批处理过程中都有相同的内容:您等待用户操作,有一个运行操作的大事件循环,它等待其他用户操作,它运行另一个事件处理操作等。

只需做目前可以做的事情以及任何异步寄存器处理程序。来自计算机系统的用户操作和异步响应并没有那么不同:)

于 2011-11-15T14:59:00.883 回答
1

这里有两个不同的问题。第一个是嵌套回调(以“watterfall”方式),第二个是调用异步函数而不知道你想要传递什么延续。

为了避免嵌套地狱,基本思想是改用名称函数。所以

f1(arg1, function(){
    arg2 = g(arg1);
    f2(function(){
        ...use arg2
    });
});

可以变成

var arg2;
f1(arg1, afterf1);

function afterf1(){
    arg2 = g(arg1);
    f2(afterf2);
}

function afterf2(){
    ...use arg2;
}

请注意,唯一的其他主要重构是我们需要将内部函数关闭的所有变量移动到外部范围,因为内部函数将不再是内部函数(尽量将共享变量保持在最低限度 - 有如果你觉得你开始得到太多的代码,有很多技巧可以将它们重构为更易于维护的代码)。

现在,另一个问题是当您使用.

在同步情况下,您可以这样做

var x = f();

任何想要 x 的人都可以在之后随时访问它。

但是在异步情况下,您只能做

f(function(x){
   ...use x here
});

唯一能看到的代码x将由这个回调控制。

然后,诀窍是有一种方法可以在之后添加额外的“真实”回调,并让您传递给原始函数的回调只是将结果传递给感兴趣的各方,而不是直接使用它。

var got_result = false;
var result = null;
var waiting_for_result = [];

function register_callback(f){
    if(got_result){
        f(result);
    }else{
        waiting_for_result.push(f);
    }
}

var real_callback = function(x){
    got_result = true;
    result = x;
    for(var i=0; i< waiting_for_result.length; i++){
        waiting_for_result[i](result);
    }
}

//

API.getCurrentLocation(real_callback);
foreach facility {
    register_callback(function(location){
        ...stuff
    })

当然,由于这样做是重复的 PITA,因此有许多 Promise 库正是这样做的。它们大多也有简洁的方法,允许您使用匿名函数执行非嵌套“命名回调”模式。

例如,在Dojo中,这可能看起来像

var location_promise = API.GetLocationPromise();
foreach facility {
    location_promise.then(function(location){
        ...use location
    });
}
于 2011-11-15T15:40:36.413 回答
0

你为什么不在循环之外定义你当前的位置?

var here ;    
API.getCurrentLocation( function( location ) {here = location.coordinates;})

    foreach facility {

        API.getFacilityLocation( function( e ) { // async, takes a callback fxn arg
          var there    = e. coordinates;
          var distance = API.calculateFrom( here, there );
        });
    }

这样你只计算一次

于 2011-11-15T13:56:21.380 回答