I'd like to know from the html below, if link[rel=import]
, link[rel=stylesheet]
, img
and script
are pending/loaded/failed/aborted without the need to add listeners beforehand and at any point after the event has happened
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="utf-8">
<link rel="import" href="template-bundle.html">
<link rel="stylesheet" href="bundle.css">
</head>
<body>
<header><img src="logo.png" alt="App logo"></header>
<!-- Boilerplate... -->
<script src="./app-bundle.js"></script>
</body>
</html>
In other words: Is there an interface which provides something similar to a Bluebird's isPending()
, isResolved()
, isRejected()
methods or a regular ES6 Promise?
Bonus question: Is this something can be achieved using a Service Worker?
Since SW can intercept requests and know their status, I was wondering if I can implement an API which returns a Promise that
load
event firederror
or aborted
was fired.Thanks for the help
Update & Solution:
Thanks to the answers of @pritishvaidya and @guest271314, I was able to come up with a viable solution using MutationObserver
that involves watching DOM for additions of resource nodes (link,img,script) and adding a promise to them that will resolve as described above
This works great, with the only caviat that the script tag needs to be inlined in <head>
before any other resource. Here's an example
var resourceNodeSelector = 'link[href],script[src],img[src]';
function watchResource (n) {
var url = n.href || n.src;
if (!n.matches || !n.matches(resourceNodeSelector)) {
return;
}
if (n.status) {
return;
}
n.status = resourceObserver.promises[url] = new Promise(function (resolve, reject) {
n.addEventListener('load', resolve);
n.addEventListener('error', reject);
n.addEventListener('abort', reject);
n.addEventListener('unload', function (l) { delete resourceObserver.promises[url]} );
});
n.status.catch(function noop () {}); //catch reject so that it doesn't cause an exception
}
var resourceObserver = new MutationObserver(function (mutations) {
document.querySelectorAll(resourceNodeSelector).forEach(watchResource);
});
resourceObserver.promises = {};
resourceObserver.observe(window.document, {childList: true, subtree: true});
Once the observer is in place, any valid resource element, should have a status
promise property that you can check at any point in time
document.querySelector('link').status.then(linkLoaded).catch(linkFailed)
A more elegant solution, that does not involve using the expensive querySelectorAll
, should be possible with ServiceWorker, since it can be programmed to intercept and keep track of all resource requests and their status
The best practice is to put JavaScript <script> tags just before the closing </body> tag rather than in the <head> section of your HTML. The reason for this is that HTML loads from top to bottom. The head loads first, then the body, and then everything inside the body.
The position in the source is irrelevant - only the position in time matters. You should avoid putting scripts in the <head> if possible as it slows down page display (see the link Alan posted).
Script tags are executed in the order they appear It also means scripts which appear later on the page can depend on things scripts which appear earlier have done. Elements on the page won't render until all the script tags preceding them have loaded and executed.
We can link JavaScript to HTML by adding all the JavaScript code inside the HTML file. We achieve this using the script tag which was explained earlier. We can put the <script></script> tag either inside the head of the HTML or at the end of the body.
You can utilize onload
, onerror
events of <link>
element; see Browser CSS/JS loading capabilities at right column.
Create an object to store status of all <link>
requests and resolved or rejected Promise
corresponding to the <link>
element.
Reject Promise
at onerror
event; use .catch()
chained to Promise.reject()
to handle error so that Promise.all()
will not stop processing resolved promises within array passed as parameter. You can also throw
error from .catch()
at onerror
handler to Promise.all()
if any rejected Promise
should stop processing of resolved promise within array of promises.
At window.onload
event handler, use Promise.all()
to process all resolved links, using same function called before window.onload
event.
To wait for results of Promise.all()
to be available, set src
of last <script>
element to bundle.js
at .then()
chained to Promise.all()
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="utf-8">
<script>
var handleLinks = {
links: [],
isPending: true
};
function handleBeforeLoad() {
if (document.querySelectorAll("link").length === 0) {
console.log("links loading state is pending..", handleLinks.isPending);
} else {
handleLinks.isPending = false;
Promise.all(handleLinks.links)
.then(function(linksContent) {
console.log("links resolved:", linksContent
, "links loading state is pending.."
, handleLinks.isPending);
linksContent.filter(Boolean).forEach(function(link) {
// `content` property : html `document`, `CSSStyleSheet`
// requested at `<link>` element
console.log(link);
});
// load `bundle.js`
document.getElementById("bundle")
.src = "bundle.js"
})
.catch(function(err) {
console.log("link error:", err.message)
})
}
}
handleBeforeLoad();
window.onload = handleBeforeLoad;
function handleLink(el) {
handleLinks.links.push(Promise.resolve({
content: el.import || el.sheet,
type: el.type,
rel: el.rel,
href: el.href,
integrity: el.integrity,
isResolved: true
}));
}
function handleLinkError(el) {
handleLinks.links.push(Promise.reject(new Error(JSON.stringify({
error: "error loading link",
type: el.type,
rel: el.rel,
href: el.href,
integrity: el.integrity,
isRejected: true
}))).catch(function(err) {
// handle error
console.log(err);
// this will return a resolved Promise
return "error requesting link " + el.href;
// `throw err` here if any rejected Promise should
// stop `Promise.all()` from handling resolved Promise
}));
}
</script>
<link onload="handleLink(this)"
onerror="handleLinkError(this)"
rel="import"
href="template-bundle.html"
type="text/html">
<link onload="handleLink(this)"
onerror="handleLinkError(this)"
rel="stylesheet"
href="bundle.css"
type="text/css">
<!-- this should throw error, file does not exist -->
<link onload="handleLink(this)"
onerror="handleLinkError(this)"
rel="stylesheet"
href="bundles.css"
type="text/css">
<body>
<header><img src="" alt="App logo"></header>
<!-- Boilerplate... -->
<script id="bundle"></script>
</body>
</html>
plnkr http://plnkr.co/edit/DQj9yTDcoQJj3h7rGp95?p=preview
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With