Skip to content

Commit

Permalink
Don't miss any error.
Browse files Browse the repository at this point in the history
- Queue errors to ensure FIFO.
- Use the workaround for buggy timers.
  • Loading branch information
rkatic committed Jun 11, 2014
1 parent 917bcb6 commit d43cb96
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 43 deletions.
35 changes: 12 additions & 23 deletions browser-asap.js
Expand Up @@ -4,8 +4,16 @@
var rawAsap = require("./raw");
// RawTasks are recycled to reduce GC churn.
var freeTasks = [];
// You're not going to believe this.
var hasSetImmediate = typeof setImmediate === "function";
// 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
Expand Down Expand Up @@ -44,34 +52,15 @@ RawTask.prototype.call = function () {
// Its name will be periodically randomized to break any code that
// depends on its existence.
asap.onerror(error);
} else if (hasSetImmediate) {
// In WebWorkers on Internet Explorer 10 and 11, the setTimeout
// function is not FIFO.
// In all other known cases, setTimeout is FIFO, including non
// Worker contexts in the exact same browsers.
// Thankfully these browsers have setImmediate, which executes
// tasks in the correct order.
// However, setImmediate in the same browsers is known to
// occassionally drop events.
// Weighing the evil of out of order errors against the evil of not
// noticing an error at all, I've elected to use `setImmediate` for
// this case.
// Note that in Internet Explorer 10, setImmediate must be called
// by name.
setImmediate(function () {
throw 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.
setTimeout(function () {
throw error;
}, 0);
pendingErrors.push(error);
requestErrorThrow();
}
} finally {
this.task = null;
freeTasks[freeTasks.length] = this;
}
};

45 changes: 25 additions & 20 deletions browser-raw.js
Expand Up @@ -92,7 +92,7 @@ var BrowserMutationObserver = global.MutationObserver || global.WebKitMutationOb
// - iPhone Safari 7-7.1
// - Safari 6-7
if (typeof BrowserMutationObserver === "function") {
requestFlush = makeRequestFlushFromMutationObserver();
requestFlush = makeRequestCallFromMutationObserver(flush);

// MessageChannels are desirable because they give direct access to the HTML
// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera
Expand Down Expand Up @@ -122,7 +122,7 @@ if (typeof BrowserMutationObserver === "function") {
// - iPad Safari 4.3
// - Lynx 2.8.7
} else {
requestFlush = makeRequestFlushFromTimer();
requestFlush = makeRequestCallFromTimer(flush);
}

// `requestFlush` requests that the high priority event queue be flushed as
Expand All @@ -134,12 +134,12 @@ rawAsap.requestFlush = requestFlush;

// To request a high priority event, we induce a mutation observer by toggling
// the text of a text node between "1" and "-1".
function makeRequestFlushFromMutationObserver() {
function makeRequestCallFromMutationObserver(callback) {
var toggle = 1;
var observer = new BrowserMutationObserver(flush);
var observer = new BrowserMutationObserver(callback);
var node = document.createTextNode("");
observer.observe(node, {characterData: true});
return function requestFlush() {
return function requestCall() {
toggle = -toggle;
node.data = toggle;
};
Expand All @@ -153,10 +153,10 @@ function makeRequestFlushFromMutationObserver() {
// page's first load. Thankfully, this version of Safari supports
// MutationObservers, so we don't need to fall back in that case.

// function makeRequestFlushFromMessageChannel() {
// function makeRequestCallFromMessageChannel(callback) {
// var channel = new MessageChannel();
// channel.port1.onmessage = flush;
// return function requestFlush() {
// channel.port1.onmessage = callback;
// return function requestCall() {
// channel.port2.postMessage(0);
// };
// }
Expand All @@ -169,9 +169,9 @@ function makeRequestFlushFromMutationObserver() {
// closure.
// Never forget.

// function makeRequestFlushFromSetImmediate() {
// return function requestFlush() {
// setImmediate(flush);
// function makeRequestCallFromSetImmediate(callback) {
// return function requestCall() {
// setImmediate(callback);
// };
// }

Expand All @@ -185,31 +185,36 @@ function makeRequestFlushFromMutationObserver() {
// approximately 7 in web workers in Firefox 8 through 18, and sometimes not
// even then.

function makeRequestFlushFromTimer() {
return function requestFlush() {
function makeRequestCallFromTimer(callback) {
return function requestCall() {
// We dispatch a timeout with a specified delay of 0 for engines that
// can reliably accommodate that request. This will usually be snapped
// to a 4 milisecond delay, but once we're flushing, there's no delay
// between events.
var timeoutHandle = setTimeout(handleFlushTimer, 0);
var timeoutHandle = setTimeout(handleTimer, 0);
// However, since this timer gets frequently dropped in Firefox
// workers, we enlist an interval handle that will try to fire
// an event 20 times per second until it succeeds.
var intervalHandle = setInterval(handleFlushTimer, 50);
function handleFlushTimer() {
// Whichever timer succeeds will cancel both timers and request the
// flush.
var intervalHandle = setInterval(handleTimer, 50);

function handleTimer() {
// Whichever timer succeeds will cancel both timers and
// execute the callback.
clearTimeout(timeoutHandle);
clearInterval(intervalHandle);
flush();
callback();
}
};
}

// This is for `asap.js` only.
// Its name will be periodically randomized to break any code that
// depends on its existence.
rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer;

// ASAP was originally a nextTick shim included in Q. This was factored out
// into this ASAP package. It was later adapted to RSVP which made further
// amendments. These decisions, particularly to marginalize MessageChannel and
// to capture the MutationObserver implementation in a closure, were integrated
// back into ASAP proper.
// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js

0 comments on commit d43cb96

Please sign in to comment.