XMLHttpRequest2 中的新技巧

简介

HTML5 世界中的无名英雄之一是 XMLHttpRequest。 严格来说,XHR2 不是 HTML5。不过,这也是我们在 Android Vitals 中 浏览器供应商对核心平台做了哪些贡献。我把 XHR2 放入了我们的新袋子里 因为它在当今复杂的 Web 应用中发挥着不可或缺的作用。

看来我们的老朋友变了大变身,但很多人 不知道它的新功能。XMLHttpRequest 级别 2 引入了大量新功能,以消除我们 Web 应用中的复杂黑客行为; 例如跨源请求、上传进度事件 并支持上传/下载二进制数据。这允许 AJAX 可与许多先进的 HTML5 API(例如 File System APIWeb Audio API、 和 WebGL。

本教程重点介绍了 XMLHttpRequest 中的一些新功能, 尤其是那些可用于处理文件的工具。

正在提取数据

通过 XHR 提取二进制 blob 形式的文件一直很痛苦。从技术上讲 这甚至不可能一个有据可查的技巧包括 使用用户定义的字符集替换 MIME 类型,如下所示。

提取图片的旧方法:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);

// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function(e) {
  if (this.readyState == 4 && this.status == 200) {
    var binStr = this.responseText;
    for (var i = 0, len = binStr.length; i < len; ++i) {
      var c = binStr.charCodeAt(i);
      //String.fromCharCode(c & 0xff);
      var byte = c & 0xff;  // byte at offset i
    }
  }
};

xhr.send();

虽然这种方法可行,但是您实际上会在 responseText 中获得什么 不是二进制 blob。它是一个表示图片文件的二进制字符串。 我们要诱骗服务器将未处理的数据传回。 虽然这个技巧有用,但我还是称呼它为黑魔法, 反对它。只要您依靠字符代码黑客手段和字符串操作, 将数据强制转换为所需的格式,就成了问题。

指定响应格式

在前面的示例中,我们将图片下载为二进制“文件” 方法是覆盖服务器的 MIME 类型并将响应文本作为二进制字符串处理。 我们改为利用 XMLHttpRequest 的新 要通知的 responseTyperesponse 属性 浏览器。

xhr.responseType
在发送请求前,请设置 xhr.responseType 转换为“text”、“arraybuffer”、“blob”或“document”,具体取决于您的数据需求。 请注意,系统会默认设置 xhr.responseType = ''(或省略) 对“text”的响应
xhr.response
请求成功后,xhr 的响应属性将 以 DOMStringArrayBufferBlobDocument(具体取决于 responseType。)

有了这个新的强大功能,我们可以修改上一个示例,但这次, 以 Blob 而非字符串的形式提取图片:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    // Note: .response instead of .responseText
    var blob = new Blob([this.response], {type: 'image/png'});
    ...
  }
};

xhr.send();

好多了!

ArrayBuffer 响应

ArrayBuffer 是用于二进制数据的通用固定长度容器。如果您熟悉这些概念, 需要原始数据的通用缓冲区,但这些人员背后的真正强大之处在于 您可以创建“数据视图”使用 JavaScript 类型数组进行底层数据的映射。 实际上,可以通过单个 ArrayBuffer 来源创建多个视图。 例如,您可以创建一个具有相同 ArrayBuffer 的 8 位整数数组 现有的 32 位整数数组。底层数据 只是为它创建不同的表示形式。

例如,以下代码会提取与 ArrayBuffer 相同的图片, 但这次, 将从该数据缓冲区创建一个无符号 8 位整数数组:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer
  // var byte3 = uInt8Array[4]; // byte at offset 4
  ...
};

xhr.send();

Blob 响应

如果您想直接与 Blob 和/或 无需操作文件的任何字节,请使用 xhr.responseType='blob'

window.URL = window.URL || window.webkitURL;  // Take care of vendor prefixes.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = this.response;

    var img = document.createElement('img');
    img.onload = function(e) {
      window.URL.revokeObjectURL(img.src); // Clean up after yourself.
    };
    img.src = window.URL.createObjectURL(blob);
    document.body.appendChild(img);
    ...
  }
};

xhr.send();

Blob 可在许多地方使用,包括 写入 indexedDB,将其写入 HTML5 文件系统, 或创建 Blob 网址,如下所示: 示例。

发送数据

能够以不同格式下载数据固然很好,但 在无法将这些富媒体格式发送回主基地(服务器)时,我们能为我们提供任何帮助。 XMLHttpRequest 限制我们发送 DOMStringDocument (XML) 数据一段时间。现在不需要了。改版后的 send() 方法已被重写,以接受以下任意类型: DOMStringDocumentFormDataBlobFileArrayBuffer。本专精课程的剩余部分 部分演示了如何使用每种类型发送数据。

发送字符串数据:xhr.send(DOMString)

function sendText(txt) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) {
    if (this.status == 200) {
      console.log(this.responseText);
    }
  };

  xhr.send(txt);
}

sendText('test string');
function sendTextNew(txt) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.responseType = 'text';
  xhr.onload = function(e) {
    if (this.status == 200) {
      console.log(this.response);
    }
  };
  xhr.send(txt);
}

sendTextNew('test string');

没有什么新内容,只是正确的代码段略有不同。 它会设置 responseType='text' 以进行比较。同样,省略这一行 会产生相同的结果。

提交表单:xhr.send(FormData)

很多人可能都习惯于使用 jQuery 插件 或其他库来处理 AJAX 表单提交。我们可以使用 FormData, 另一种针对 XHR2 设计的新数据类型。FormData 有助于使用 JavaScript 即时创建 HTML <form>。 然后可以使用 AJAX 提交该表单:

function sendForm() {
  var formData = new FormData();
  formData.append('username', 'johndoe');
  formData.append('id', 123456);

  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);
}

从本质上讲,我们只是动态创建了 <form>,并添加了 <input> 值。

当然,您无需从头开始创建 <form>FormData 对象可以通过现有的 HTMLFormElement 进行初始化 。例如:

<form id="myform" name="myform" action="/server">
  <input type="text" name="username" value="johndoe">
  <input type="number" name="id" value="123456">
  <input type="submit" onclick="return sendForm(this.form);">
</form>
function sendForm(form) {
  var formData = new FormData(form);

  formData.append('secret_token', '1234567890'); // Append extra data before send.

  var xhr = new XMLHttpRequest();
  xhr.open('POST', form.action, true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);

  return false; // Prevent page from submitting.
}

HTML 表单可包含上传文件(例如 <input type="file">FormData 也可以处理它。只需附加文件,浏览器就会 在调用 send() 时构造 multipart/form-data 请求:

function uploadFiles(url, files) {
  var formData = new FormData();

  for (var i = 0, file; file = files[i]; ++i) {
    formData.append(file.name, file);
  }

  var xhr = new XMLHttpRequest();
  xhr.open('POST', url, true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);  // multipart/form-data
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  uploadFiles('/server', this.files);
}, false);

上传文件或 blob:xhr.send(Blob)

我们还可以使用 XHR 发送 FileBlob 数据。 请注意,所有 File 都是 Blob,因此两者皆可在此处使用。

此示例使用 Blob() 构造函数从头开始创建一个新的文本文件。 并将该 Blob 上传到服务器。该代码还会设置一个处理程序 来通知用户上传进度:

<progress min="0" max="100" value="0">0% complete</progress>
function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  // Listen to the upload progress.
  var progressBar = document.querySelector('progress');
  xhr.upload.onprogress = function(e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(['hello world'], {type: 'text/plain'}));

上传字节:xhr.send(ArrayBuffer)

最后但同样重要的一点是,我们可以将 ArrayBuffer 作为 XHR 的载荷发送。

function sendArrayBuffer() {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  var uInt8Array = new Uint8Array([1, 2, 3]);

  xhr.send(uInt8Array.buffer);
}

跨域资源共享 (CORS)

CORS 允许 Web 应用 以便在一个域上向另一个域发出跨域 AJAX 请求。 启用此功能非常简单,只需服务器发送一个响应标头即可。

启用 CORS 请求

假设您的应用位于 example.com 上,而您 想从 www.example2.com 提取数据。通常来说,如果您 进行此类 AJAX 调用时,请求会失败,浏览器 就会抛出来源不匹配错误使用 CORS,www.example2.com 只需添加一个标头,即可选择允许来自 example.com 的请求:

Access-Control-Allow-Origin: http://example.com

可向单个资源添加 Access-Control-Allow-Origin 特定网站下或整个网域中。要允许任何网域 请求,请设置:

Access-Control-Allow-Origin: *

实际上,此网站 (html5rocks.com) 已在其所有网页上启用了 CORS。点燃 然后您会看到 Access-Control-Allow-Origin

<ph type="x-smartling-placeholder">
</ph> html5rocks.com 上的 Access-Control-Allow-Origin 标头
html5rocks.com 上的`Access-Control-Allow-Origin` 标头

启用跨源请求非常简单,因此如果您的数据是公开的,请启用 CORS

发出跨网域请求

如果服务器端点已启用 CORS,则发出跨源请求 与普通的 XMLHttpRequest 请求没有什么不同。例如: example.com现在可以向 www.example2.com

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
  var data = JSON.parse(this.response);
  ...
}
xhr.send();

实际示例

下载并将文件保存到 HTML5 文件系统

假设您有一个图库,并希望从图片库中 然后使用 HTML5 文件系统将其保存在本地。 实现此目的的方法之一是以 Blob 形式请求图片 并使用 FileWriter 将其写出:

window.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem;

function onError(e) {
  console.log('Error', e);
}

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {

  window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
    fs.root.getFile('image.png', {create: true}, function(fileEntry) {
      fileEntry.createWriter(function(writer) {

        writer.onwrite = function(e) { ... };
        writer.onerror = function(e) { ... };

        var blob = new Blob([xhr.response], {type: 'image/png'});

        writer.write(blob);

      }, onError);
    }, onError);
  }, onError);
};

xhr.send();

分割文件并上传各个部分

使用 File API,我们可以将 上传大文件的工作。具体方法是将上传内容分成多个数据块 为每个部分生成一个 XHR,并将该文件放在服务器上。这是 类似于 Gmail 快速上传大型附件的方式。这种技术也可以 用于规避 Google App Engine 的 32MB http 请求限制。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

})();

用于在服务器上重建文件的代码未显示。

参考