%PDF- %PDF-
Direktori : /proc/985914/root/data/old/home/stash/stash/atlassian-stash/static/util/ |
Current File : //proc/985914/root/data/old/home/stash/stash/atlassian-stash/static/util/performance.js |
/** * This module holds functions for improving the performance of JS code. * Currently it contains queueDOMRead and queueDOMWrite, which can be used to ensure all DOM reads are executed at * once, and all DOM writes are executed at once to avoid reflows that occur when reads and writes are interleaved. * * See also: http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/ */ define('util/performance', [ 'jquery' ], function ( $ ) { 'use strict'; /** * This creates a queue of function callbacks. When the first callback is added (using the queue function), * the whole queue is registered to be invoked as soon as the current JS event loop finishes executing * (with a setTimeout(...,0)). If any other callbacks get registered in the meantime, they'll all be executed right * after the first one. Then the queue will reset itself to empty and await the next callback to be added. The * queue takes in a callback (executedCallback) that will be called whenever the queue is executed and reset. * * You can also force immediate queue execution without waiting for the setTimeout (forceExecute). Below I use * this to avoid superfluous setTimeouts. If the Read queue executes, it will call the Write queue immediately * afterwards, which will call the Read queue in case anything was added during the Write callbacks, and so forth * until there are no more new callbacks being added to either queue. * @param {Function} executedCallback - a callback which will be called whenever the queue is executed. * @return {Object} */ function getDelayedExecutionQueue(name, executedCallback) { var queue = new $.Callbacks(); var executing = false; var pendingExecution = null; function execute() { pendingExecution = null; executing = true; // execute all the pending callbacks queue.fire(); // reset the queue queue = new $.Callbacks(); executing = false; // call the callback since we just executed the queue. executedCallback(); } return { // Helps with testing name : name, /** * If there are callbacks scheduled to be executed, remove the scheduled execution, execute them now * and reset the queue. */ forceExecute : function() { if (pendingExecution) { clearTimeout(pendingExecution); execute(); } }, /** * Add a callback to the queue. If there is no scheduled execution for the queue, schedule one. * @param fn {Function} the callback to add */ queue : function(fn) { if (executing) { // if we're currently executing, there is no harm executing this immediately. fn(); } else { // otherwise, add it to the queue for executing later. queue.add(fn); if (!pendingExecution) { // if there isn't an execution scheduled yet, schedule one. pendingExecution = setTimeout(execute, 0); } } } }; } // create a separate queue for DOM reads and DOM writes. Avoid the overhead of setTimeout by forcing immediate // execution of the partner queue when all your own callbacks have finished. var DOMReadQueue = getDelayedExecutionQueue('READ', function() { DOMWriteQueue.forceExecute(); }); var DOMWriteQueue = getDelayedExecutionQueue('WRITE', function() { DOMReadQueue.forceExecute(); }); /** * Returns a function that ensures that no more than `opt_queueMax` callbacks are pending at any time. This is useful * to ensure that multiple calls don't build up in the queue. * * @example * * $(window).on('scroll', enqueueCapped(requestAnimationFrame, somethingExpensive)); * * @param {Function} queuingFn - A function that accepts functions to execute. * @param {Function} queuedFn - The function that should be enqueued. * @param {number} [opt_queueMax=1] - The maximum number of pending executions before further enqueue requests are ignored. * @returns {Function} */ function enqueueCapped(queuingFn, queuedFn, opt_queueMax) { opt_queueMax = opt_queueMax || 1; var waiting = 0; return function enqueue() { if (waiting >= opt_queueMax) { return; } waiting++; queuingFn(function dequeue() { try { return queuedFn.apply(this, arguments); } finally { waiting--; } }); }; } /** * Return a function that will async map through items in the input array, returning a promise that will resolve * to the output array when it's complete. * * The batch size is adaptive, but you can set min and max values to constrain it. * * We return a Deferred rather than a Promise. You can reject or resolve the deferred yourself and no more batches will run. * * @param {Function} fn - iterator * @param {Object} [batchLimits] * @param {number} [batchLimits.initial=500] * @param {number} [batchLimits.min=10] * @param {number} [batchLimits.max=Infinity] * @param {Function} [runBatch] - if you want to run the batch in a wrapper function * @returns {Function} */ function frameBatchedMap(fn, batchLimits, runBatch) { batchLimits = $.extend({ initial : 500, min : 10, max : Infinity }, batchLimits); runBatch = runBatch || function(fn) { fn(); }; function clamp(n) { return Math.min(batchLimits.max, Math.max(batchLimits.min, n)); } return function(arr) { var batchSize = batchLimits.initial; var deferred = $.Deferred(); var i = 0; var out = []; function singleBatch() { for (var end = Math.min(i + batchSize, arr.length); i < end; i++) { out[i] = fn(arr[i]); } } requestAnimationFrame(function loop() { if (deferred.state() !== 'pending') { return; } var start = new Date().getTime(); runBatch(singleBatch); var end = new Date().getTime(); var timeSpent = end - start; // adjust so the next batch takes 15ms // Assumes each item takes the same amount of time // batchSize / timeSpent == newBatchSize / 15ms // newBatchSize = 15 * batchSize / timeSpent // We use ceil to get an integer out of it (that isn't 0) // We clamp this within the requested min/max limits batchSize = clamp(Math.ceil(batchSize * (15 / timeSpent))); if (i === arr.length) { deferred.resolve(out); } else { requestAnimationFrame(loop); } }); return deferred; }; } return { queueDOMRead : DOMReadQueue.queue.bind(DOMReadQueue), queueDOMWrite : DOMWriteQueue.queue.bind(DOMWriteQueue), enqueueCapped : enqueueCapped, frameBatchedMap : frameBatchedMap }; });