首页 > 解决方案 > web worker - 传回主线程时数据未定义

问题描述

我正在尝试使用网络工作者获取一些数据,然后将它们传递回主线程。我已尝试使用此代码,但无法按预期工作

onmessage = (e) => {
  console.log(e);
  if( e.data[0] === 'fetchData' ){
    fetch('https://example.com/platform/api/v1/endpoint')
    .then( (res) => res.json() )
    .then( async (data) => {
      const imagesData = await Promise.all(
        data.map( async (item) => {
          let res = await fetch(item.src);
          let img = await res.blob();
          let reader = new FileReader();
          reader.readAsDataURL(img);
          reader.onloadend = () => { 
            return { 
              title: item.title, 
              link: item.link, 
              src: reader.result 
            }
          }
        })
      )
      postMessage(imagesData);
    })
  }
}

a 之后的 imagesDataconsole.log将包含一个由七个未定义元素组成的数组,我该如何解决这个问题?

更新

我已经在 vue 前端和工作人员中更改了代码,现在我能够获取数据,但有时工作人员将无法工作,我将无法获取数据,或者我只得到两个条目但预期number 是前端的七个项目。这是我修改代码的方式,也许我需要在使用另一个代码之前终止工作?注意:我正在创建一个选项卡覆盖 chrome 扩展

vue前端代码

<script>
const worker = new Worker('services.js');

export default {
  name: 'App',
  beforeCreate() {
    worker.postMessage(['fetchData']);
  },
  created() {
    this.init();
    this.clock();
  },
  data() {
    return {
      mostVisited: [],
      imagesData: [],
      isLoading: true 
    }
  },
  methods: {
    init() {
      worker.onmessage = (e) => {
        console.log(e);
        this.imagesData = e.data; 
        this.isLoading = false; 
      }
      browser.topSites.get().then( (sites) => this.mostVisited = sites ); 
    } //end init
  }
</script>

网络工作者代码

onmessage = (e) => {
  console.log(e);
  if( e.data[0] === 'fetchData' ){
    fetch('https://example.com/platform/api/v1/endpoint')
    .then( (res) => res.json() )
    .then( async (data) => {
      let imagesData = [];
      await Promise.all(
        data.map( async (item) => {
          let res = await fetch(item.src);
          let img = await res.blob();
          let reader = new FileReader();
          reader.readAsDataURL(img);
          reader.onloadend = () => { 
            imagesData.push({ title: item.title, link: item.link, src: reader.result });
          }
        })
      )
      postMessage(imagesData);
    }); // end then(data)
  }
}

标签: javascriptvue.jsweb-worker

解决方案


在解析外部 Promise 之前,您不会等待异步 FileReader 读取您的文件,因此Promise 在之后但之前Promise.all解析。let img = await res.blob();onloadend

由于您处于 Worker 上下文中,因此您可以使用同步 FileReaderSync API,它会提供类似

.then( async (data) => {
  let imagesData = [];
  await Promise.all(
    data.map( async (item) => {
      let res = await fetch(item.src);
      let img = await res.blob();
      let reader = new FileReaderSync();
      const result = reader.readAsDataURL(img);
      imagesData.push({ title: item.title, link: item.link, src: result });
    })
  )
  postMessage(imagesData);
});

但我有 99% 的信心认为您甚至不需要这些数据:URL,而且它的危害比任何事情都大。

请记住,来自 FileReader 的 data: URL 始终编码为 base64,这将生成一个包含 134% 原始数据的 DOMString,因为 DOMStrings 存储在 UTF-16 中,所以您将其乘以 2。
然后,要将数据传递到主上下文,浏览器必须使用结构化克隆算法序列化数据,对于 DOMStrings,这意味着在内存中的简单副本。现在,每个图像占用的内存大约是其实际大小的 5 倍,甚至在主上下文开始再次将其解析为二进制数据之前,它就可以构建像素数据......

相反,只需将获得的 Blob 传递为img.
Blob 的内部数据由结构化克隆算法通过引用传递,因此您复制的所有内容都是小型 js 包装器。
然后,当您需要在主上下文中显示这些图像时,使用URL.createObejctURL(img)which 将返回一个 blob: URL 直接指向同一个 Blob 的数据,该数据仍仅在内存中存储一​​次。

.then( async (data) => {
  let imagesData = [];
  await Promise.all(
    data.map( async (item) => {
      let res = await fetch(item.src);
      let img = await res.blob();
      const url = URL.createObjectURL(img);
      imagesData.push({ title: item.title, link: item.link, src: url, file: img });
    })
  )
  postMessage(imagesData);
});

推荐阅读