Last active
December 19, 2015 15:28
-
-
Save getify/5976429 to your computer and use it in GitHub Desktop.
comparison of my proposal for script preloading vs. jaffathecake's proposal. "ex1-getify" and "ex1-jaffathecake" compare implementations for a simple script loader with serial request-order executions. "ex2-getify" and "ex2-jaffathecake" compare implementations of a more complex/capable loader that supports "sub-groups" of ASAP execution order s…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The semantics of THIS script loader are to preload all the | |
// specified script URLs in parallel, and wait until all are preloaded | |
// before executing them. They will execute strictly in request-order. | |
// | |
// NOTE: multiple calls to loadScripts() create separate "groups" of | |
// scripts that will operate independently and NOT affect other groups. | |
function loadScripts() { | |
function checkListeners() { | |
// are all scripts done preloading? | |
if (!listeners.some(function(listener){ | |
return (listeners[i].check !== true); | |
})) { | |
// execute all scripts now! | |
listeners.forEach(function(listener){ | |
// marks the script as OK to execute now | |
listener.script.fulfilled = true; | |
}); | |
} | |
} | |
var listeners = []; | |
// start preloading the scripts | |
Array.prototype.slice.call(arguments) | |
.forEach(function(src){ | |
var script = document.createElement("script"); | |
script.preload = true; // only proposed! | |
script.src = src; | |
document.head.appendChild(script); | |
// set up script preload listener | |
var listener = { script: script }; | |
listener.check = (function(){ | |
this.check = true; // overwrite the listener func with `true` | |
checkListeners(); | |
}).bind(listener); | |
listeners.push(listener); | |
script.addEventListener("preload",listener.check); // only proposed! | |
}); | |
} | |
// NOTE: these two groups of scripts will load independently | |
// will preload these in parallel as a group | |
loadScripts( | |
"a.js", // executes "a.js" once both scripts are (pre)loaded | |
"b.js" // then executes "b.js" | |
); | |
// will preload these in parallel as a group | |
loadScripts( | |
"c.js", // executes "c.js" once all 4 scripts are (pre)loaded | |
"d.js", // then executes "d.js" | |
"e.js", // then executes "e.js" | |
"f.js" // then executes "f.js" | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The semantics of THIS script loader are to preload all the | |
// specified script URLs in parallel, and wait until all are preloaded | |
// before executing them. They will execute strictly in request-order. | |
// | |
// NOTE: multiple calls to loadScripts() create separate "groups" of | |
// scripts that will operate independently and NOT affect other groups. | |
function loadScripts() { | |
// TODO: | |
// Unfortunately, this implementation doesn't actually match the | |
// intended semantics. There's no logic here that prevents the first | |
// script from executing until all the scripts have finished loading. | |
// The end result is that here, the first script might run well | |
// earlier than second script, which could create unfortunate side- | |
// effects such as rendering part of something before rendering | |
// the other part. The intended semantics keep the executions of | |
// all scripts as close together as possible, to minimize such | |
// noticeable gaps in execution. | |
// | |
// The current suggestion is to either hide the entire page while | |
// these gaps might be happening (sort of a nuclear UX option), or | |
// try to have this generalized script loader figure out what parts of | |
// the page it would need to hide while leaving other unaffected | |
// stuff visible. The former seems terribly unwanted, and the latter | |
// seems basically impossible (for a general script loader agnostic of | |
// what the scripts do which it loads). | |
// start preloading the scripts | |
Array.prototype.slice.call(arguments) | |
.forEach(function(src,srcIdx){ | |
var link = document.createElement("link"); | |
link.rel = "subresource"; | |
link.href = src; | |
document.head.appendChild(link); | |
var script = document.createElement("script"); | |
if (srcIdx > 0) { | |
script.dependencies = 'script[src="' + arguments[srcIdx-1] '"]'; | |
} | |
script.src = src; | |
document.head.appendChild(script); | |
}); | |
} | |
// NOTE: these two groups of scripts will load independently | |
// will preload these in parallel as a group | |
loadScripts( | |
"a.js", // executes "a.js" once both scripts are (pre)loaded | |
"b.js" // then executes "b.js" | |
); | |
// will preload these in parallel as a group | |
loadScripts( | |
"c.js", // executes "c.js" once all 4 scripts are (pre)loaded | |
"d.js", // then executes "d.js" | |
"e.js", // then executes "e.js" | |
"f.js" // then executes "f.js" | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The semantics of THIS script loader are to preload all the | |
// specified script URLs in parallel, but start executing as soon as possible, | |
// maintaining the overall execution order as specified. This differs from | |
// the one above in that it doesn't wait for all to finish before starting | |
// execution. | |
// | |
// In addition, if any param to loadScripts() is an array, the URLs in that | |
// array are treated as siblings and can be executed in any order, meaning | |
// they won't block each other. Kind of like a sub-grouping where ASAP | |
// execution order is OK. | |
// | |
// Finally, this script loader stops executing subsequent scripts if any | |
// script fails to load. | |
// | |
// NOTE: multiple calls to loadScripts() create separate "groups" of | |
// scripts that will operate independently and NOT affect other groups. | |
function loadScripts() { | |
function checkExecution() { | |
if (abort) return; | |
subgroups.some(function(subgroup,subgroupIdx){ | |
if (!subgroup.done) { | |
// the subgroup is done only if all scripts in it have run | |
subgroup.done = !subgroup.some(function(listener){ | |
return (listeners[i].ran !== true); | |
}); | |
// is the current group "done" and there's a subsequent | |
// subgroup of scripts to signal? | |
if (subgroup.done && subgroupIdx < subgroups.length - 1) { | |
// signal all the scripts in the next subgroup | |
// as ready to execute whenever they finish loading | |
subgroups[subgroupIdx+1].forEach(function(listener){ | |
listener.script.fulfilled = true; | |
}); | |
} | |
else { | |
return true; // break out of the loop | |
} | |
} | |
}); | |
} | |
var abort = false, subgroups = []; | |
// start preloading the scripts | |
Array.prototype.slice.call(arguments) | |
.forEach(function(src,subgroupIdx){ | |
if (abort) return; | |
var subgroup = []; | |
subgroup.done = false; | |
if (!Array.isArray(src)) src = [src]; | |
src.forEach(function(url){ | |
var script = document.createElement("script"); | |
// only the script(s) beyond the *first* subgroup | |
// need to defer execution | |
if (subgroupIdx > 0) { | |
script.preload = true; // only proposed! | |
} | |
script.src = url; | |
document.head.appendChild(script); | |
// set up script execution listener | |
// NOTE: only need to save a script reference for scripts | |
// we're actually preloading | |
var listener = { script: (subgroupIdx > 0 ? script : {}) }; | |
listener.ran = function(){ | |
this.ran = true; // overwrite the listener func with `true` | |
checkExecution(); | |
}.bind(listener); | |
subgroup.push(listener); | |
// NOTE: no need to listen for `preload` event for THIS | |
// loader's semantics | |
script.addEventListener("load",listener.ran); | |
// script error so we can pause/abort? | |
script.addEventListener("error",function(){ abort = true; }); | |
}); | |
subgroups.push(subgroup); | |
}); | |
} | |
// NOTE: these two groups of scripts will load independently | |
// will preload these in parallel as a group | |
loadScripts( | |
"a.js", // executes "a.js" first ASAP | |
"b.js" // then executes "b.js" | |
); | |
// will preload these in parallel as a group | |
loadScripts( | |
"c.js", // executes "c.js" first ASAP | |
["d.js","e.js"], // then subgroup executes "d.js" or "e.js" in either order | |
"f.js" // then executes "f.js" | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The semantics of THIS script loader are to preload all the | |
// specified script URLs in parallel, but start executing as soon as possible, | |
// maintaining the overall execution order as specified. This differs from | |
// the one above in that it doesn't wait for all to finish before starting | |
// execution. | |
// | |
// In addition, if any param to loadScripts() is an array, the URLs in that | |
// array are treated as siblings and can be executed in any order, meaning | |
// they won't block each other. Kind of like a sub-grouping where ASAP | |
// execution order is OK. | |
// | |
// Finally, this script loader stops executing subsequent scripts if any | |
// script fails to load. | |
// | |
// NOTE: multiple calls to loadScripts() create separate "groups" of | |
// scripts that will operate independently and NOT affect other groups. | |
function loadScripts() { | |
// TODO: | |
// The capability of this proposed implementation to respond | |
// to a script loading error by preventing subsequent script | |
// executions is based on some assumptions that are unconfirmed | |
// as of yet. It's not clear if `dependencies` is sensitive to | |
// a script resulting in a load failure or not. Moreover, will | |
// `dependencies` be sensitive to compile-time or run-time errors | |
// in a matching script and thus not proceed to execute? | |
var dependencies; | |
// start loading the scripts | |
Array.prototype.slice.call(arguments) | |
.forEach(function(src,subgroupIdx){ | |
if (!Array.isArray(src)) src = [src]; | |
src.forEach(function(url){ | |
var link = document.createElement("link"); | |
link.rel = "subresource"; | |
link.href = url; | |
document.head.appendChild(link); | |
var script = document.createElement("script"); | |
// only the script(s) beyond the *first* subgroup | |
// depend on script(s) | |
if (subgroupIdx > 0) { | |
script.dependencies = dependencies; | |
} | |
script.src = url; | |
document.head.appendChild(script); | |
// TODO: unclear if this is necessary or not. Will `dependencies` | |
// automatically prevent a script from running if one of the | |
// dependencies in question results in a load-error, or do we | |
// need to handle it manually as shown here? | |
// script error so we can pause/abort? | |
script.addEventListener("error",function(){ | |
// find all subsequent scripts that are waiting on the current | |
// subgroup's scripts | |
var waitingScripts = document.querySelectorAll( | |
'script[src="' + src.join('"], script[src="') + '"]' | |
); | |
// (temporarily) pause all of the waiting scripts... | |
waitingScripts.forEach(function(script){ | |
// ...by forcing an impossible `dependencies` value, for now | |
// TODO: would this actually work? | |
script.dependencies += ', script[src="http://nonexistant.tld"]'; | |
}); | |
}); | |
}); | |
// save dependencies for next subgroup to depend on | |
dependencies = 'script[src="' + src.join('"], script[src="') + '"]'; | |
}); | |
} | |
// NOTE: these two groups of scripts will load independently | |
// will preload these in parallel as a group | |
loadScripts( | |
"a.js", // executes "a.js" first ASAP | |
"b.js" // then executes "b.js" | |
); | |
// will preload these in parallel as a group | |
loadScripts( | |
"c.js", // executes "c.js" first ASAP | |
["d.js","e.js"], // then subgroup executes "d.js" or "e.js" in either order | |
"f.js" // then executes "f.js" | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment