如何避免回调“瀑布”?

发布于 2024-12-15 18:55:02 字数 830 浏览 1 评论 0原文

除了相对琐碎的功能之外,我倾向于害怕编写 Javascript 的原因之一是,当一件事真正依赖于另一件事时,我从未找到一种合适的方法来避免回调瀑布。有这样的做法吗?

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

我有一组设施,我需要计算距用户当前位置的距离。这需要获取用户的当前位置(只需发生一次),并在循环访问设施位置时获取每个设施的位置并计算距离。检索位置(长/纬度)的 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 和闭包的语言激增,我一直认为一定有人找到了一种方法来保持这些瀑布的可管理性,但我还没有找到如何组织这样一个解决方案的良好解释。

有什么建议或提示吗?

任何见解将不胜感激。

One of the reasons that I tend to dread writing Javascript for anything other than relatively trivial bits of functionality is that I've never found a decent approach for avoiding the callback waterfall when one thing really depends on another. Is there any such approach?

I'm working on a Titanium app right now and bumping into this real world scenario:

I have a set of facilities for which I need to the calculate distance from the user's current location. This entails getting the user's current position (which only needs to happen once) and, while looping through the facility locations, getting the position for each one and calculating the distance. The APIs that retrieve locations (long/lat) are asynchronous so the "easy" approach looks like this (pseudo-code follows):

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

Because this is all in a loop, though, I'm calculating my current position each time -- more work than I really need to do. I haven't yet managed to refactor this in such a way that I'm only getting the current location once and still having that location available for the distance calculation.

Given the explosion of languages that support lambdas and closures, I keep thinking that someone must have found an approach to keep these waterfalls manageable, but I have yet to find a good explanation of how to organize such a solution.

Any suggestions or hints?

Any insight would be tremendously appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

葬シ愛 2024-12-22 18:55:02

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

例子:

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

The basic strategy: don't use anonymous functions for callbacks, and move the loop into the callback that runs when the current location is returned.

Example:

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);
×纯※雪 2024-12-22 18:55:02

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

只需执行以下操作即可此刻以及任何异步寄存器处理程序。用户操作和计算机系统的异步响应没有太大不同:)

You must start to think more event-oriented. Define function for each callback level and provide it as argument when needed, and don't think of it as callback waterfall. Note that you have the same in each non-batch process: you wait for user actions, there's a big event loop that runs action, which waits for other user action, which runs another event processing action etc.

Simply do what can be done at the moment and for anything asynchronous register handler. User actions and async responses from computer systems are not so different :)

月下客 2024-12-22 18:55:02

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

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

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

There are two separate problems here. The first is nesting callbacks (in an "watterfall" manner) and the second is calling an async function without knowing what continutation you want to pas it.

To avoid nesting hell the basic idea is to use names functions instead. So

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

Can become

var arg2;
f1(arg1, afterf1);

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

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

Note that the only other main refactoring is that we need to move all the variables the inner functions closed over to the outer scope, since the inner functions won't be inner functions anymore (do try to keep shared variables at a minimum - there are many tricks to refactor them into more mantainable code if you feel you are starting to get too many of them).

Now, the other problem is having a callback you don't know when you you use the value of.

In a synchronous case you can do

var x = f();

and whoever wants x can just just access it anytime afterwards.

But in the async case you are limited to doing

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

And the only code that will ever be able to see x will be controlled by this callback.

The trick then is having a way to add extra "real" callbacks afterwards and have the callback you passed to the original function just pass on the result to the interested parties, instead of using it directly.

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

Of course, since doing this is a repetitive PITA, there are many Promise libraries that do precisely this. They mostly also have neat methods that allow you do the non-nesting "named callbacks" pattern with anonymous functions as well.

For example, in Dojo this might look like

var location_promise = API.GetLocationPromise();
foreach facility {
    location_promise.then(function(location){
        ...use location
    });
}
小耗子 2024-12-22 18:55:02

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

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

这样你只计算一次

why dont you define your current location outside the loop ?

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

this way you only calculate it once

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文