【问题标题】:Is it possible to get a callback on a parent function from an inner promise?是否可以从内部承诺中获取父函数的回调?
【发布时间】:2016-05-09 10:08:05
【问题描述】:

Play with my JSfiddle

是否可以使用$timeout 承诺使scrollTo 可链接,如下所示?如果没有,我该如何编写 scrollTo,作为一个承诺,以便我可以使用 then 进行回调?

html:

<div ng-click=toTop()>click me</div>

js:

app.controller('MainCtrl', function($scope, $timeout) {
    $scope.toTop = function() {
        var bodyEl = angular.element(document.querySelector('#body'));
        scrollTo_(bodyEl[0], 0, 500);

    };


    function scrollTo_(element, to, duration) {
        if (duration <= 0) {
                        $timeout.cancel(forward);
            console.log('yoyo');
            return forward;
        }
        var difference = to - element.scrollTop;
        var perTick = difference / duration * 10;
        var forward = $timeout(function() {
            element.scrollTop = element.scrollTop + perTick;
            scrollTo_(element, to, duration - 10);
        }, 10, false);
    }
});

【问题讨论】:

  • 能否提供HTML、CSS。请创建 jsFiddle
  • 已更新。还有什么我可以帮忙的吗?

标签: javascript angularjs recursion timeout promise


【解决方案1】:

很难解释“使用 $timeout 承诺使 scrollTo 可链接,如下所示”,因为 scrollTo 不可链接。因此,让我们假设 scrollTo 将被设为“thenable”,方法是让它返回一个 $q 承诺。

有了这个假设,返回的 Promise 应该在滚动完成时解决,或者如果滚动在完成之前停止(通过另一个 scrollTo_() 调用)被拒绝,这似乎是合理的。

这里是:

var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, $q) {
    var stopSignal = false;
    $scope.toTop = function() {
        var element = angular.element(document.querySelector('#body'))[0];
        stopSignal = true; // stop previous.
        scrollTo_(0, 500).then(function() { // Yay, scrollTo_() is thenable!
            console.log('scroll complete');
        }, function() {
            console.log('scroll stopped');
        });
        function scrollTo_(to, duration) {
            var startTime = null,
                startPos = element.scrollTop,
                dfrd = $q.defer();
            function step(timestamp) {
                if(stopSignal) {
                    // This block kills an animation that's in progress.
                    // It doesn't affect freshly initiated animations.
                    stopSignal = false; // reset the `stopSignal` flag.
                    dfrd.reject(); // reject the deferred, to indicate non-completion
                    return; // prevent further animation by returning early and killing the recursion.
                }
                if (!startTime) {
                    stopSignal = false;
                    startTime = timestamp;
                }
                var progress = timestamp - startTime;
                if (progress < duration) {
                    element.scrollTop = startPos + (to - startPos) * progress / duration; // linear movement w.r.t. time, though not necessarily at regular intervals.
                    window.requestAnimationFrame(step); // recurse
                } else {
                    element.scrollTop = to; // ensure final position is accurate
                    dfrd.resolve();
                }
            }
            // This is horribly messy but necessary(?) for the stop signal to take effect.
            window.requestAnimationFrame(function() {
                stopSignal = false;
                window.requestAnimationFrame(step);
            });
            return dfrd.promise;
        }
    };
});

DEMO 双击查看滚动效果在另一个开始之前停止。

如您所见,通过删除 $timeout 和 :

避免了 10 毫秒超时引起的间隔
  • 利用window.requestAnimationFrame(),这是编排动画的现代方式(尽管目前浏览器兼容性是一个问题,尤其是 IE
  • 从既定的基准时间开始在每一步工作,从而提供更好的运动线性保证。

不久之后每个人都应该以这种方式编写浏览器动画。

【讨论】:

  • 伙计,这是一个很好的答案。您节省了我下一步重构为 requestanimationframe 的时间
  • 耶!出于某种原因,`window.requestAnimationFrame(),` 让我有轻微的脑力劳动,尽管它不应该因为它只是 window.setTimeout() 的一个微妙变体。
  • 我只会在接下来几天的旅行中使用手机。但是,如果你有时间,我有几个问题要问你。 1. 我们能否将 toscroll_ 重构到 scope.totop 之外,以便我可以从另一个函数中调用它。 2. 你能注释掉 dfrd.reject 块吗,因为我不太明白? 3. 我应该把cancelRequestAnimation放在哪里?最后,(困难) 4. 你检查过 rxjs 和 observable 吗?如果这是一个可观察的而不是一个承诺,是否可以取消按键上的动画?我将制作一个 ng2 plnkr 来测试这个 observable
  • 最后,一旦我有时间测试,上述问题都不会阻止我将其标记为正确。只是你看起来知识渊博,这些是我接下来要研究和实施的任务
  • 1.毫无疑问是的,尽管我并不是 Angular 方面的专家。正如目前所写的那样,scrollTo_() 依赖于关闭它的一些数据,因此重构将是一个相当大的变化。可能最好作为一个单独的 SO 问题来解决。 2. 完成 3. 需要作为 (1) 的一部分来处理。 4. 据我所知,这个问题都是关于离散事件的。 FRP 是处理数据流的一种很酷的方式,但在这里似乎不相关。
【解决方案2】:

这就是我将如何完成递归承诺:

function scrollTo_(element, to, duration) {
    if(duration<=0) return Promise.resolve();
    var difference = to - element.scrollTop,
        perTick = difference / duration * 10;
    return $timeout(function() {
        element.scrollTop = element.scrollTop + perTick;
    }, 10, false).then(function(){
      duration -= 10;
      return scrollTo_(element, to, duration)
    });
}

编辑:

如果你想停止中间的超时,我想你可以这样做:

var scrollTopTimeoutFlag = true, scrollTopTimeout;
function scrollTo_(element, to, duration) {
    if(duration<=0 || !scrollTopTimeoutFlag){
      if(scrollTopTimeout)  return $timeout.cancel(scrollTopTimeout);
      return $q.when();   // $q.when == Promise.resolve
    } 
    var difference = to - element.scrollTop,
        perTick = difference / duration * 10;
    scrollTopTimeout = $timeout(function() {
        element.scrollTop = element.scrollTop + perTick;
    }, 10, false);
    return scrollTopTimeout.then(function(){
      duration -= 10;
      return scrollTo_(element, to, duration)
    });
}

【讨论】:

  • 标记为正确但你能想出一个方法来保持 $timeout.cancel(timeout) 的承诺吗?
  • 此外,有没有办法使用 $q 而不是 Promise.resolve 来做到这一点?
猜你喜欢
  • 2015-07-25
  • 1970-01-01
  • 1970-01-01
  • 2021-02-24
  • 1970-01-01
  • 1970-01-01
  • 2019-07-15
  • 2019-04-15
  • 2016-05-22
相关资源
最近更新 更多