【问题标题】:Releasing the UI to update with deferreds释放 UI 以使用延迟更新
【发布时间】:2013-12-24 08:48:46
【问题描述】:

我喜欢 then/deferred/promises 的外观,但我希望长时间运行的处理不会阻塞 UI 渲染。在下面的代码示例中,我喜欢fill2 的风格,但更喜欢fill3 的行为。嵌套的setTimeout 很丑。网络工作者可能是一种选择,但它们存在相当多的开销和兼容性问题。有没有办法简单地做到这一点?

我在http://jsfiddle.net/ubershmekel/8mtbM/1/做了一个可点击的活生生的例子

<!DOCTYPE html>
<html lang="en-us">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
</head>
<body>
<h1>First</h1>
<script>
var setTimeoutDelay = 50; // I noticed that 0 releases the UI in IE10 but not fully in Chrome31 or FF25.

var time = function() {
    return (new Date).getTime();
}

busy = function() {
    var start = time();
    while (time() - start < 2000) {

    }
    console.log('dn ' + time());
}

pin = function(i) {
    $('body').append($('<h1>' + i + '</h1>'));
}

clear = function() {
    $('body').html('');
}

fill = function() {
    busy();
    pin(1);
    busy();
    pin(2);
    busy();
    pin(3);
}

fill2 = function() {
    var def = $.Deferred();
    def.then(function() {
        busy();
        pin(1);
    }).then(function() {
        busy();
        pin(2);
    }).then(function() {
        busy();
        pin(3);
    });
    def.resolve();
}

fill3 = function() {
    setTimeout(function() {
        busy();
        pin(1);
        setTimeout(function() {
            busy();
            pin(2);
            setTimeout(function() {
                busy();
                pin(3);
            }, setTimeoutDelay);
        }, setTimeoutDelay);
    }, setTimeoutDelay);
}


$(function() {
    var start = function() {
        clear();
        setTimeout(fill1);
        //setTimeout(fill2);
        //setTimeout(fill3);
    };

    setTimeout(start);
});
</script>
</body>
</html>

【问题讨论】:

  • fill3 允许渲染继续的原因是您正在使用 setTimeout,它的作用类似于异步方法,因为它会在继续之前等待几秒钟。不幸的是,使用延迟复制它的唯一方法是也使用 setTimeout。最好的解决方案是修复 busy 所代表的任何内容,使其不会阻塞渲染。
  • busy 是几个长时间运行的函数,下载一个文件,解析它,然后递归遍历一个图。每一个都可能是几秒钟。
  • 对,但这不应该阻止渲染。如果是,那么它很可能会被优化或修改为不会阻塞渲染。

标签: javascript jquery asynchronous promise


【解决方案1】:

我写了一个jQuery插件,我称之为.thenst。这只是一个then,继续下一步使用setTimeout。我不确定它总体上对人类有多大用处,但它解决了这个问题。

这是http://jsfiddle.net/ubershmekel/8mtbM/2/的演示

插件代码

(function ($) {
    /*
    Give deferred objects a `.thenst` function which chains like `then`
    but adds a `setTimeout` in between to allow for UI responsiveness.
    This is extra useful for chaining ~1-second long functions.
    */

    var origDeferred = $.Deferred;
    $.Deferred = function (func) {
        var thenst = function (fnDone, fnFail, fnProgress) {
            var newDef = $.Deferred();
            var args;
            var runsAfterSetTimeout = function () {
                newDef.then(fnDone);
                newDef.resolveWith(newDef, args);
            };
            def.then(function () {
                args = arguments;
                setTimeout(runsAfterSetTimeout, 0)
            }, fnFail, fnProgress);
            return newDef;
        };

        // Hooking jQuery's Deferred is hard because we have to modify Deferred()
        // and Deferred.promise() though a Deferred has all the methods of a promise.
        // It would be nicer if we could only extend the `promise  ` object.
        var def = origDeferred(func);
        def.thenst = thenst;
        var origPromise = def.promise;
        def.promise = function (obj) {
            obj = origPromise(obj);
            obj.thenst = thenst;
            return obj;
        }
        return def;
    };

}(jQuery));

这是示例页面,其中包含示例页面,以防 jsfiddle 失败。

<!DOCTYPE html>
<html lang="en-us">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
</head>
<body>

<p>Notice how fillLinear/fillDefer don't let you see the numbers were cleared. The button doesn't become depressed. And you don't see the numbers updating as they're pinned. fillSetTimeout works well but is hard to read and write. The performance difference between fillSetTimeout and fillThenst is inconsisntent and is probably garbage-collection and redrawing-timing related.</p>
<button onclick="fillLinear()">fillLinear</button>
<button onclick="fillDefer()">fillDefer</button>
<button onclick="fillSetTimeout()">fillSetTimeout</button>
<button onclick="fillThenst()">fillThenst</button>
<div id="panel"></div>


<script>

(function ($) {
    /*
    Give deferred objects a `.thenst` function which chains like `then`
    but adds a `setTimeout` in between to allow for UI responsiveness.
    This is extra useful for chaining ~1-second long functions.
    */

    var origDeferred = $.Deferred;
    $.Deferred = function (func) {
        var thenst = function(fnDone, fnFail, fnProgress) {
            var newDef = $.Deferred();
            var args;
            var runsAfterSetTimeout = function() {
                /*if ($.isFunction(fnDone)) {
                    // call the function and report it's done
                    fnDone.apply(newDef, args);
                } else {
                    // fnDone should be a promise object like:
                    //     $.Deferred().thenst($.ajax());
                    // We want to trigger that promise to run, I'm not sure if this is the way to do that.
                    // Though this point is kinda moot for $.ajax as it would fire off at instantiation.
                    newDef.then(fnDone);*/
                newDef.then(fnDone);
                newDef.resolveWith(newDef, args);
            };
            def.then(function() { args = arguments; setTimeout(runsAfterSetTimeout, 0) }, fnFail, fnProgress);
            return newDef;
        };

        // Hooking jQuery's Deferred is hard because we have to modify Deferred()
        // and Deferred.promise() though a Deferred has all the methods of a promise.
        // It would be nicer if we could only extend the `promise  ` object.
        var def = origDeferred(func);
        def.thenst = thenst;
        var origPromise = def.promise;
        def.promise = function(obj) {
            obj = origPromise(obj);
            obj.thenst = thenst;
            return obj;
        }
        return def;
    };

}(jQuery));


var setTimeoutDelay = 50; // I noticed that 0 releases the UI in IE10 but not fully in Chrome31 or FF25.

var time = function() {
    return (new Date).getTime();
}

busy = function() {
    var start = time();
    while (time() - start < 1000) {

    }
    console.log('dn ' + time());
}

pin = function(i) {
    $('#panel').append($('<h1>' + i + '</h1>'));
}

bclear = function() {
    $('#panel').html('');
}

fillLinear = function() {
    bclear();
    pin(0);
    busy();
    pin(1);
    busy();
    pin(2);
    busy();
    pin(3);
}

fillDefer = function() {
    bclear();
    pin(0);
    var def = $.Deferred();
    def.then(function() {
        busy();
        pin(1);
    }).then(function() {
        busy();
        pin(2);
    }).then(function() {
        busy();
        pin(3);
    });
    def.resolve();
}

fillSetTimeout = function() {
    bclear();
    pin(0);
    setTimeout(function() {
        busy();
        pin(1);
        setTimeout(function() {
            busy();
            pin(2);
            setTimeout(function() {
                busy();
                pin(3);
            }, setTimeoutDelay);
        }, setTimeoutDelay);
    }, setTimeoutDelay);
}

fillThenst = function() {
    bclear();
    pin(0);
    var def = $.Deferred();
    def.thenst(function() {
        busy();
        pin(1);
    }).thenst(function() {
        busy();
        pin(2);
    }).thenst(function() {
        busy();
        pin(3);
    });
    def.resolve();
}

</script>
</body>
</html>

PS thenst 代表 then-setTimeout。听起来没有创意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    • 2013-12-28
    相关资源
    最近更新 更多