Javascript Bacon.js懒惰求值,鼠标拖动示例在没有log()语句的情况下中断

Javascript Bacon.js懒惰求值,鼠标拖动示例在没有log()语句的情况下中断,javascript,reactive-programming,frp,bacon.js,Javascript,Reactive Programming,Frp,Bacon.js,我试图理解bacon.js和FRP,因此尝试制作一个简单的拖放示例,但我在对一段代码进行惰性计算时遇到了问题。当我将.log()添加到流中时,它看起来和行为都很好,但如果我将其取出,它不会更新。以下是我正在做的: // UI streams block_mousedown = block_el.asEventStream('mousedown').map(xyFromEvent); global_mousemove = html.asEventStream('mousemove').map(x

我试图理解bacon.js和FRP,因此尝试制作一个简单的拖放示例,但我在对一段代码进行惰性计算时遇到了问题。当我将
.log()
添加到流中时,它看起来和行为都很好,但如果我将其取出,它不会更新。以下是我正在做的:

// UI streams
block_mousedown  = block_el.asEventStream('mousedown').map(xyFromEvent);
global_mousemove = html.asEventStream('mousemove').map(xyFromEvent);
global_mouseup   = html.asEventStream('mouseup');

// Composites
isDragging    = block_mousedown.merge(global_mouseup.map(0));
mouseDragging = Bacon.combineAsArray(isDragging, global_mousemove)
    .filter(function(v){ return notZero(v[0]) })

mouseDeltaFromClick = mouseDragging
    .map(getDelta)

// Block offset when it was clicked on
block_pos_at_mousedown = block_mousedown
    .map( function(a,b){ return block_el.offset();})
    .map(function(e){ return [e.left, e.top]; })
    // If I remove this log(), it doesn't evaluate
    .log();

// merge mouse delta with block position when clicked    
mouseDeltaAndBlockPos = mouseDeltaFromClick
    .combine(block_pos_at_mousedown, '.concat')
    .onValue( function(e){
        block_el.css({
            top  : e[3]+e[1]+"px",
            left : e[2]+e[0]+"px"
        });
    });    
这里有一个


我在想我可能做得不对,这是正确的方法吗?我想通过单击时块的位置,该位置应在
mousedown
上更新,但不能随
mousemove
一起更新。您描述的行为与惰性评估几乎没有关系:问题的根源是执行顺序

在你的代码中(没有
log()
on
block\u pos\u at mousedown
mousedeltaffomclick
似乎在
block\u pos\u at mousedown
之前发生了变化(我必须说我不知道
log()
到底是如何改变顺序的)。等一下

observable.combine
方法期望将
属性
作为第一个参数-
EventStream
将自动转换您传递的。现在,
mouseDeltaAndBlockPos
mousedeltafarmclick
block\u pos\u mousedown
更改时更改(并因此触发所有回调)

因此,当
mousedeltaffomclick
block\u pos\u at mousedown
之前触发时,代码末尾的回调将使用新的增量调用,但使用旧的块位置(因为
back\u pos\u at mousedown
已转换为
属性
)。旧值为
[0,0]
,因此每次单击时块都会捕捉到左上角

如何修复它?安全的方法是对不相关回调的执行顺序不做任何假设,并记住这一点再写一遍。我想到了这个:

function xyFromEvent(v){ return [v.clientX, v.clientY]; }

function getDelta(t){
    var a = t[1];
    var b = t[0];
    return [a[0]-b[0], a[1]-b[1]]; 
}

function add(p1, p2) {
    return [p1[0] + p2[0], p1[1] + p2[1]];   
}

$().ready(function () {
    var block = $("#clickable-block");
    var html  = $("html");

    var blockDragging = block.asEventStream('mousedown').map(true)
                             .merge(html.asEventStream('mouseup').map(false))
                             .toProperty(false);

    var deltas = html.asEventStream('mousemove').map(xyFromEvent).slidingWindow(2,2).map(getDelta);

    // Just like deltas, but [0,0] when user is not dragging the box.
    var draggingDeltas = Bacon.combineWith(function(delta, dragging) {
        if(!dragging) {
            return [0, 0];
        }
        return delta;
    }, deltas, blockDragging);

    var blockPosition = draggingDeltas.scan([0,0], add);

    blockPosition.onValue(function(pos) {
        block.css({
            top  : pos[1] + "px",
            left : pos[0] + "px"
        });
    });
});
和jsFiddle:


编辑:在评论中,raimohanska
提出了另一种使用
flatMap
的解决方案:

我同意,如果您的代码依赖于在特定时间调用的map函数,那么延迟求值会导致意外行为。卢卡斯的回答在我看来仍然很好。除了我会使用flatMap,比如Wolfflow的绘图示例:在Google群组中也有一个相关的线程:实际上这有点坏了。这里有一个更好的:漂亮的!我已在答案中添加了此解决方案。