【问题标题】:FormData in a webworker - in some browsers - is this wrong?Webworker 中的 FormData - 在某些浏览器中 - 这是错误的吗?
【发布时间】:2015-03-09 18:15:04
【问题描述】:

我一直在玩网络工作者内部的上传,发现在 Chrome 中可以正常工作。但是,在 Safari 和 Firefox 中,我得到 FormData 是未定义的。

我发现这很好并且是意料之中的:正如https://stackoverflow.com/a/13970107/1238884 中提到的那样,Webworkers 没有定义/支持 FormData 并实现了一个 polyfill。 (注:更新的 polyfill @https://gist.github.com/Rob--W/8b5adedd84c0d36aba64

但为什么它可以在 Chrome (v39) 中运行?它是否有一个错误的实现,或者他们故意放在那里?

【问题讨论】:

    标签: javascript google-chrome web-worker form-data


    【解决方案1】:

    DOM 只在单线程浏览器端运行良好 - 出于这个原因,Web Worker 故意没有直接(可写)访问 DOM ... 当然,您可以随意发布消息以跨地址空间复制值

    【讨论】:

      【解决方案2】:

      FormData 没有在某些浏览器上定义,但 File 也有。这里是不使用 File 对象的 FormData 代码:

      /*
       * FormData for XMLHttpRequest 2  -  Polyfill for Web Worker  (c) 2012 Rob W
       * License: Creative Commons BY - http://creativecommons.org/licenses/by/3.0/
       * - append(name, value[, filename])
       * - toString: Returns an ArrayBuffer object
       * 
       * Specification: http://www.w3.org/TR/XMLHttpRequest/#formdata
       *                http://www.w3.org/TR/XMLHttpRequest/#the-send-method
       * The .append() implementation also accepts Uint8Array and ArrayBuffer objects
       * Web Workers do not natively support FormData:
       *                http://dev.w3.org/html5/workers/#apis-available-to-workers
       **/
      (function() {
          // Export variable to the global scope
          (this == undefined ? self : this)['FormData'] = FormData;
      
          var ___send$rw = XMLHttpRequest.prototype.send;
          XMLHttpRequest.prototype['send'] = function(data) {
              if (data instanceof FormData) {
                  if (!data.__endedMultipart) data.__append('--' + data.boundary + '--\r\n');
                  data.__endedMultipart = true;
                  this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + data.boundary);
                  data = new Uint8Array(data.data).buffer;
              }
              // Invoke original XHR.send
              return ___send$rw.call(this, data);
          };
      
          function FormData() {
              // Force a Constructor
              if (!(this instanceof FormData)) return new FormData();
              // Generate a random boundary - This must be unique with respect to the form's contents.
              this.boundary = '------RWWorkerFormDataBoundary' + Math.random().toString(36);
              var internal_data = this.data = [];
              /**
              * Internal method.
              * @param inp String | ArrayBuffer | Uint8Array  Input
              */
              this.__append = function(inp) {
                  var i=0, len;
                  if (typeof inp === 'string') {
                      for (len=inp.length; i<len; i++)
                          internal_data.push(inp.charCodeAt(i) & 0xff);
                  } else if (inp && inp.byteLength) {/*If ArrayBuffer or typed array */
                      if (!('byteOffset' in inp))   /* If ArrayBuffer, wrap in view */
                          inp = new Uint8Array(inp);
                      for (len=inp.byteLength; i<len; i++)
                          internal_data.push(inp[i] & 0xff);
                  }
              };
          }
          /**
          * @param name     String                                  Key name
          * @param value    String|Blob|File|Uint8Array|ArrayBuffer Value
          * @param filename String                                  Optional File name (when value is not a string).
          **/
          FormData.prototype['append'] = function(name, value, filename) {
              if (this.__endedMultipart) {
                  // Truncate the closing boundary
                  this.data.length -= this.boundary.length + 6;
                  this.__endedMultipart = false;
              }
              var valueType = Object.prototype.toString.call(value),
                  part = '--' + this.boundary + '\r\n' + 
                      'Content-Disposition: form-data; name="' + name + '"';
      
              if (/^\[object (?:Blob|File)(?:Constructor)?\]$/.test(valueType)) {
                  return this.append(name,
                                  new Uint8Array(new FileReaderSync().readAsArrayBuffer(value)),
                                  filename || value.name);
              } else if (/^\[object (?:Uint8Array|ArrayBuffer)(?:Constructor)?\]$/.test(valueType)) {
                  part += '; filename="'+ (filename || 'blob').replace(/"/g,'%22') +'"\r\n';
                  part += 'Content-Type: application/octet-stream\r\n\r\n';
                  this.__append(part);
                  this.__append(value);
                  part = '\r\n';
              } else {
                  part += '\r\n\r\n' + value + '\r\n';
              }
              this.__append(part);
          };
      })();
      

      【讨论】:

        【解决方案3】:

        Chrome 从 36.0.1935.0 (crbug.com/360546) 版本开始支持 Web Workers 中的 FormData。

        它的存在是因为 latest specification of FormData 要求它暴露给 Worker 上下文。 Firefox 尚未实现此功能,但已引起关注 (bugzil.la/739173)。

        我认为您误读了my answer that you've linked。不支持new FormData(&lt;HTMLFormElement&gt;);,因为不支持采用&lt;form&gt; 并基于表单元素初始化其字段的构造函数,因为&lt;form&gt; 元素显然不能在Web 工作者中创建。但是您可以创建一个空的FormData 对象并根据需要使用它(如果浏览器实现了最新版本的规范)。

        如果您想在所有当前浏览器中使用FormData API,那么您必须加载您在问题中引用的我的polyfill。如果检测到 FormData API 已定义,则此 polyfill 会提前返回,因此它不会在已经支持 FormData 的浏览器中引起问题。请注意,与本机 FormData API 相比,此 polyfill 效率较低,因为 polyfill 必须预先创建完整的字符串(在内存中),而本机实现只能保存文件的轻型数据结构,并从上传文件/Blob 时的磁盘。

        对于小块数据,这不是问题,但如果您打算上传大文件,那么您最好使用 postMessage 将数据传递到主线程(如果您使用 typed,则使用 transferables数组)并在那里构造 XMLHttpRequest 对象,然后发送它。 Web Worker 主要用于卸载 CPU 繁重的任务,但 XMLHttpRequest 主要是网络(发生在单独的 IO 线程上,至少在 Chrome 中),因此在这方面使用 Web Worker 对主线程没有好处。

        【讨论】:

        • 我的应用程序使用相当大的 Blob(通常为几百兆)执行长时间运行(阻塞)任务,并让工作人员执行 - 这涉及多个 XHR 步骤 - 它们阻止主线程抛出“等待”屏幕(我可能做错了)。无论如何,我很高兴 FormData 正在为工人服务。为回复干杯!
        • >“在这方面,使用 Web Workers 对主线程没有任何好处。”。也许我错过了一些东西,但是由于主线程不再支持同步的 AJAX 请求,因此它们必须在工作线程中使用(我认为异步 AJAX 请求不会在工作线程中工作(如果他们应该在哪里返回数据)线程提前结束?) - 但也许我错了)。而且我看不出如何按特定顺序发送和接收请求。
        • @StanE “我认为异步 AJAX 请求不会在工作线程中工作” - 错了,异步请求在工作线程中是完全正常的。如果线程终止,则请求可能会被取消(例如在服务器发送对请求的回复之前卸载页面时发生的情况)。按顺序发送请求很容易,只需等待请求完成(通过事件)。
        • 好吧,感谢您的“不费吹灰之力”。也许你只是不需要它们。使用事件通常是糟糕的设计(尤其是在线程中)并且不必要的复杂 - 你需要发出“链式请求”......呃。测试也变得复杂。
        • @StanE "no-brainer" 并不是贬义的。我的意思是,无论是同步还是异步,按顺序发送请求都不需要任何努力和资源。事件是 JavaScript 和 DOM 的核心概念,使用它是不错的设计。如果您更喜欢“顺序”语法,请查看 Promises 等。还可以看看fetch API,这是一个用于生成网络请求并使用 Promises 进行控制流(而不是事件)的 API。
        猜你喜欢
        • 1970-01-01
        • 2012-11-09
        • 2015-05-04
        • 1970-01-01
        • 2023-03-23
        • 1970-01-01
        • 2015-11-20
        • 1970-01-01
        • 2010-12-29
        相关资源
        最近更新 更多