首页 > 解决方案 > 向 XMLHttpRequest 添加 onerror 函数

问题描述

这是使用 JavaScript 中的 HTTP 请求预加载文件并将它们保存到浏览器缓存的代码。问题是如果没有您的帮助,我无法找到跟踪preloadMe功能错误的解决方案:)

我知道我们可以添加onerror并且preloadOne效果很好:

xhr.onerror = function() {
    console.log('onerror happend');
};

但我想知道我们如何在preloadMe函数中做到这一点oncompleteonfetchedonprogress。像这样的东西:

preload.onerror = items => {
   console.log('onerror happend'); // onerror here
}

非常感谢任何意见或修改...

到目前为止,这是我的代码:

(function(global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.Preload = factory());
}(this, (function() {
  'use strict';

  function preloadOne(url, done) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';
    xhr.onprogress = event => {
      if (!event.lengthComputable) return false
      let item = this.getItemByUrl(event.target.responseURL);
      item.completion = parseInt((event.loaded / event.total) * 100);
      item.downloaded = event.loaded;
      item.total = event.total;
      this.updateProgressBar(item);
    };
    xhr.onload = event => {
      let type = event.target.response.type;
      let blob = new Blob([event.target.response], {
        type: type
      });
      let url = URL.createObjectURL(blob);
      let responseURL = event.target.responseURL;
      let item = this.getItemByUrl(responseURL);
      item.blobUrl = url;
      item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
      item.type = type;
      item.size = blob.size;
      done(item);
    };
    xhr.send();
  }

  function updateProgressBar(item) {
    var sumCompletion = 0;
    var maxCompletion = this.status.length * 100;

    for (var itemStatus of this.status) {
      if (itemStatus.completion) {
        sumCompletion += itemStatus.completion;
      }
    }
    var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);

    if (!isNaN(totalCompletion)) {
      this.onprogress({
        progress: totalCompletion,
        item: item
      });
    }
  }

  function getItemByUrl(rawUrl) {
    for (var item of this.status) {
      if (item.url == rawUrl) return item
    }
  }

  function fetch(list) {
    return new Promise((resolve, reject) => {
      this.loaded = list.length;
      for (let item of list) {
        this.onerror(item); // onerror here
        this.status.push({
          url: item
        });
        this.preloadOne(item, item => {
          this.onfetched(item);
          this.loaded--;
          if (this.loaded == 0) {
            this.oncomplete(this.status);
            resolve(this.status);
          }
        });
      }
    });
  }

  function Preload() {
    return {
      status: [],
      loaded: false,
      onprogress: () => {},
      oncomplete: () => {},
      onfetched: () => {},
      onerror: () => {}, // onerror here
      fetch,
      updateProgressBar,
      preloadOne,
      getItemByUrl
    }
  }

  return Preload;

})));

//preload here
preloadMe();

function preloadMe() {

  const preload = Preload();

  preload.fetch([
    'https://round-arm-authority.000webhostapp.com/Ultimate%20Video%20Hack/videos/vid1.mp4'

  ]).then(items => {
    // use either a promise or 'oncomplete'
    console.log(items);
  });

  preload.oncomplete = items => {
    console.log(items);
  }

  preload.onerror = items => {
    console.log('onerror happend'); // onerror here
  }

  preload.onprogress = event => {
    console.log(event.progress + '%');
  }

  preload.onfetched = item => {
    console.log(item);
  }

};

标签: javascript

解决方案


有几个选项。您可以将错误回调添加为参数,就像您对done回调所做的那样。

如果你想让错误回调可选,你会做这样的事情:

function preloadOne(url, done, error) {
  const xhr = new XML HttpRequest();
  // ...
  if(typeof error === 'function') xhr.onerror = error;
  // ...
}

在某些方面,这可能更可取,因为您可以在调用之前注册错误处理程序xhr.send()。(虽然我不知道这是否重要,但我不确定事件循环在这方面是如何工作的。)

另一种方法是return xhrpreloadOne函数结束时,如下所示:

  function preloadOne(url, done) {
    const xhr = new XMLHttpRequest();
    // ...
    xhr.send();
    return xhr;
  }

然后,您可以在事后轻松添加自己的事件处理程序:

var preload = preloadOne('https://my.site.com/path', x=> {
  console.log('done');
});
preload.onerror = function(event) {
  console.log('error');
};

编辑:解决方案的事件循环注意事项return xhr

经过一番研究,我相信如果在xhr.send(). 这是基于MDN 事件循环页面,特别是该Adding messages部分,其中说:

在 Web 浏览器中,只要事件发生并附加一个事件侦听器,就会添加消息。如果没有侦听器,则事件丢失。

因此,虽然在 JavaScript 堆栈为空之前不会处理消息,但 XHR 错误的消息排队几乎随时可能发生。或尝试排队,如果尚未附加处理程序。

但是,如果您真的想返回xhr并确保附加事件处理程序的机会,我相信有一种方法可以做到这一点,方法是preoloadOne将.xhr.sendsetTimeout

setTimeout(()=> xhr.send(), 0);

这会将事件扔到send事件队列中,这意味着它必须等待 JavaScript 调用堆栈清空,然后再进入事件循环的下一个循环。并且当调用堆栈清空时,应该附加任何错误事件处理程序。


推荐阅读