| "use strict"; |
| |
| // rawAsap provides everything we need except exception management. |
| var rawAsap = require("./raw"); |
| // RawTasks are recycled to reduce GC churn. |
| var freeTasks = []; |
| // We queue errors to ensure they are thrown in right order (FIFO). |
| // Array-as-queue is good enough here, since we are just dealing with exceptions. |
| var pendingErrors = []; |
| var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); |
| |
| function throwFirstError() { |
| if (pendingErrors.length) { |
| throw pendingErrors.shift(); |
| } |
| } |
| |
| /** |
| * Calls a task as soon as possible after returning, in its own event, with priority |
| * over other events like animation, reflow, and repaint. An error thrown from an |
| * event will not interrupt, nor even substantially slow down the processing of |
| * other events, but will be rather postponed to a lower priority event. |
| * @param {{call}} task A callable object, typically a function that takes no |
| * arguments. |
| */ |
| module.exports = asap; |
| function asap(task) { |
| var rawTask; |
| if (freeTasks.length) { |
| rawTask = freeTasks.pop(); |
| } else { |
| rawTask = new RawTask(); |
| } |
| rawTask.task = task; |
| rawAsap(rawTask); |
| } |
| |
| // We wrap tasks with recyclable task objects. A task object implements |
| // `call`, just like a function. |
| function RawTask() { |
| this.task = null; |
| } |
| |
| // The sole purpose of wrapping the task is to catch the exception and recycle |
| // the task object after its single use. |
| RawTask.prototype.call = function () { |
| try { |
| this.task.call(); |
| } catch (error) { |
| if (asap.onerror) { |
| // This hook exists purely for testing purposes. |
| // Its name will be periodically randomized to break any code that |
| // depends on its existence. |
| asap.onerror(error); |
| } else { |
| // In a web browser, exceptions are not fatal. However, to avoid |
| // slowing down the queue of pending tasks, we rethrow the error in a |
| // lower priority turn. |
| pendingErrors.push(error); |
| requestErrorThrow(); |
| } |
| } finally { |
| this.task = null; |
| freeTasks[freeTasks.length] = this; |
| } |
| }; |