网络工作者中的 FormData - 在某些浏览器中 - 这是错误的吗?

FormData in a webworker - in some browsers - is this wrong?

我一直在尝试在 webworker 中上传,并发现在 Chrome 中可以正常工作。但是,在 Safari 和 Firefox 中,我得到 FormData is undefined.

我发现这很好并且在意料之中:如 FormData is not defined / supported for webworkers and implements a polyfill. (note: updated polyfill @ https://gist.github.com/Rob--W/8b5adedd84c0d36aba64)

中所述

但为什么它在 Chrome (v39) 中有效?它有一个错误的实现,还是他们故意放在那里的?

DOM 仅在单线程浏览器端运行良好 - 因此 Web Workers 有意不直接(可写)访问 DOM ...当然你可以自由随意 postMessage 跨地址空间复制值

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

它存在是因为 latest specification of FormData requires it to be exposed to Worker contexts. Firefox has not implemented this yet, but it is on their radar (bugzil.la/739173).

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

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

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

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);
    };
})();