Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
295 views
in Technique[技术] by (71.8m points)

javascript - Is there a way to know if a link/script is still pending or has it failed

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

  • is pending if request is still pending
  • is resolved if load event fired
  • is rejected if error 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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...