Skip to content

Instantly share code, notes, and snippets.

@getify
Last active December 19, 2015 15:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save getify/5976429 to your computer and use it in GitHub Desktop.
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…
// 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"
);
// 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"
);
// 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"
);
// 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