Fetch API 提供了两种不太一样的方式用于创建 Request 对象的副本:使用 Request 构造函数和使用 clone() 方法。
使用 Request 构造函数克隆
将 Request 实例作为 input 参数传给 Request 构造函数,会得到该请求的一个副本:
let r1 = new Request('https://foo.com');
let r2 = new Request(r1);
console.log(r2.url); // https://foo.com/
如果再传入 init 对象,则 init 对象的值会覆盖源对象中同名的值:
let r1 = new Request('https://foo.com');
let r2 = new Request(r1, {method: 'POST'});
console.log(r1.method); // GET
console.log(r2.method); // POST
这种克隆方式并不总能得到一模一样的副本。最明显的是,第一个请求的请求体会被标记为'已使用':
let r1 = new Request('https://foo.com', {
method: 'POST',
body: 'foobar'
});
let r2 = new Request(r1);
console.log(r1.bodyUsed); // true
console.log(r2.bodyUsed); // false
如果源对象与创建的新对象不同源,则 referrer 属性会被清除。此外,如果源对象的 mode 为 navigate,则会被转换为 same-origin。
使用 clone() 方法克隆
第二种克隆 Request 对象的方式是使用 clone() 方法,这个方法会创建一模一样的副本,任何值都不会被覆盖。与第一种方式不同,这种方法不会将任何请求的请求体标记为'已使用':
let r1 = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
let r2 = r1.clone();
console.log(r1.url); // https://foo.com/
console.log(r2.url); // https://foo.com/
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
bodyUsed 的限制
如果请求对象的 bodyUsed 属性为 true(即请求体已被读取),那么上述任何一种方式都不能用来创建这个对象的副本。在请求体被读取之后再克隆会导致抛出 TypeError。
let r = new Request('https://foo.com');
r.clone();
new Request(r);
// 没有错误
r.text(); // 设置 bodyUsed 为 true
r.clone();
// TypeError: Failed to execute 'clone' on 'Request': Request body is already used
new Request(r);
// TypeError: Failed to construct 'Request': Cannot construct a Request with a Request object that has already been used.
在 fetch() 中使用 Request 对象
fetch() 和 Request 构造函数拥有相同的函数签名并不是巧合。在调用 fetch() 时,可以传入已经创建好的 Request 实例而不是 URL。与 Request 构造函数一样,传给 fetch() 的 init 对象会覆盖传入请求对象的值:
let r = new Request('https://foo.com');
// 向 foo.com 发送 GET 请求
fetch(r);
// 向 foo.com 发送 POST 请求
fetch(r, { method: 'POST' });
fetch() 会在内部克隆传入的 Request 对象。与克隆 Request 一样,fetch() 也不能拿请求体已经用过的 Request 对象来发送请求:
let r = new Request('https://foo.com', {
method: 'POST',
body: 'foobar'
});
r.text();
fetch(r);
// TypeError: Cannot construct a Request with a Request object that has already been used.
关键在于,通过 fetch 使用 Request 会将请求体标记为已使用。也就是说,有请求体的 Request 只能在一次 fetch 中使用。(不包含请求体的请求不受此限制。)演示如下:
let r = new Request('https://foo.com', {
method: 'POST',
body: 'foobar'
});
fetch(r);
fetch(r);
// TypeError: Cannot construct a Request with a Request object that has already been used.
要想基于包含请求体的相同 Request 对象多次调用 fetch(),必须在第一次发送 fetch() 请求前调用 clone():
let r = new Request('https://foo.com', {
method: 'POST',
body: 'foobar'
});
// 3 个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);

