释放UI以使用延迟更新

时间:2013-12-19 23:24:00

标签: javascript jquery asynchronous promise

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

我在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>

1 个答案:

答案 0 :(得分:0)

我写了一个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。听起来没有创意。