ES6 Fetch API的用法与使用

1,111 阅读15分钟

Fetch 是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。fetch不是ajax的进一步封装,而是原生js。Fetch函数就是原生js,没有使用XMLHttpRequest对象

  • fetch(url, options).then() 之前,初始化Ajax一般使用jQuery的ajax方法:
$.ajax('some-url', { \
success: (data) => { /* do something with the data */ }, 
error: (err) => { /* do something when an error happens */} 
});

也可以不用jQuery,但不得不使用XMLHttpRequest,然而这是[相当复杂]

//1.创建Ajax对象
if(window.XMLHttpRequest){
   var oAjax=new XMLHttpRequest();
}else{
   var oAjax=new ActiveXObject("Microsoft.XMLHTTP");
}

//2.连接服务器(打开和服务器的连接)
oAjax.open('GET', url, true);

//3.发送
oAjax.send();

//4.接收
oAjax.onreadystatechange=function (){
   if(oAjax.readyState==4){
       if(oAjax.status==200){
          //alert('成功了:'+oAjax.responseText);
          fnSucc(oAjax.responseText);
       }else{
          //alert('失败了');
          if(fnFaild){
              fnFaild();
          }
       }
    }
};

使用步骤
1.创建XmlHttpRequest对象
2.调用open方法设置基本请求信息
3.设置发送的数据,发送请求
4.注册监听的回调函数
5.拿到返回值,对页面进行更新

注意:fetch规范与Jquery.ajax()主要有三种方式不同:

1.当接收到一个代表错误的Http状态码时,从fetch()返回的Promise不会被标记为reject,即使响应的http状态码时404或500.它将Promise状态标记为resolve,但是会将resolve的返回值ok属性设置为false。仅当网络故障时或者请求被阻止时,才会标记为reject。

2.fetch()不会接受跨域的cookies,也不能使用fetch建立跨域会话,其他网站的set-cookies头部字段将会被无视。

Why Fetch

XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。 Fetch 的出现就是为了解决 XHR 的问题,拿例子说明: 使用 XHR 发送一个 json 请求一般是这样:

var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';

xhr.onload = function() {
  console.log(xhr.response);
};

xhr.onerror = function() {
  console.log("Oops, error");
};

xhr.send();

使用 Fetch 后,

fetch(url)
.then(function(response) {return response.json();})
.then(function(data) {console.log(data);})
.catch(function(e) {console.log("Oops, error");});

使用 ES6 的 箭头函数

fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))

现在看起来好很多了,但这种 Promise 的写法还是有 Callback 的影子,而且 promise 使用 catch 方法来进行错误处理的方式有点奇怪。不用急,下面使用 async/await 来做最终优化:

try {
  let response = await fetch(url);
  let data = await response.json();
  console.log(data);
} catch(e) {
  console.log("Oops, error", e);
}
// 注:这段代码如果想运行,外面需要包一个 async function

使用 await 后,写异步代码就像写同步代码一样爽await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try...catch 捕获。 Promise,

image.png Fetch API 已经作为现代浏览器中异步网络请求的标准方法,其使用 Promise 作为基本构造要素。

Fetch 在主流浏览器中都有很好的支持,除了IE。 幸运的是,引入下面这些 polyfill 后可以完美支持 IE8+ :

  1. 由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
  2. 引入 Promise 的 polyfill: es6-promise
  3. 引入 fetch 探测库:fetch-detector
  4. 引入 fetch 的 polyfill: fetch-ie8 Fetch polyfill 的基本原理是探测是否存在 window.fetch 方法,如果没有则用 XHR 实现。这也是github/fetch 的做法,但是有些浏览器(Chrome 45)原生支持 Fetch,但响应中有中文时会乱码,老外又不太关心这种问题,所以才有了 fetch-detector 和 fetch-ie8 只在浏览器稳定支持 Fetch 情况下才使用原生 Fetch。这些库现在 每天有几千万个请求都在使用,绝对靠谱*! 终于,引用了这一堆 polyfill 后,可以愉快地使用 Fetch 了

Fetch 基本用法

实现一个简单的 fetch 请求

fetch('http://example.com/movies.json')
  .then(function(response) {return response.json();})
  .then(function(myJson) {console.log(myJson);
  .catch(function(error) {console.log(error);});

fetch 方法接受两个参数:一个 URL 地址或一个 request 对象 和 (可选的)一个配置项对象。
除了传给 fetch() 一个 URL 地址,还可以通过使用 Request() 构造函数来创建一个 request 对象,然后再作为参数传给 fetch() 方法。

let myHeaders = new Headers();
 
let myInit = { method: 'GET',
               headers: myHeaders,
               mode: 'cors',
               cache: 'default' 
               };
 
let myRequest = new Request('flowers.jpg', myInit);
 
fetch(myRequest)
.then(function(response) {return response.blob();})
.then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

接下来介绍下fetch的语法:

/** 参数: input:定义要获取的资源。可能的值是:一个URL或者一个Request对象。
   init:可选,是一个对象,
   参数有:
   method: 请求使用的方法,如 GET、POST,可选的值有 GET、POST、 PUT、 DELETE、OPTION、HEAD等。。 
   headers: 请求的头信息,形式为 Headers 对象或 ByteString值的对象字面量, 
   body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。 
   mode: 请求的模式,如 cors、 no-cors 或者 same-origin,默认为no-cors,该模式允许来自 CDN 的脚本、其他域的图片和其他一些跨域资源,但是首先有个前提条件,就是请求的 method 只能是HEAD、GET 或 POST。此外,如果 ServiceWorkers 拦截了这些请求,它不能随意添加或者修改除这些之外 Header 属性。第三,JS 不能访问 Response 对象中的任何属性,这确保了跨域时 ServiceWorkers 的安全和隐私信息泄漏问题。cors模式允许跨域请求,same-origin模式对于跨域的请求,将返回一个 error,这样确保所有的请求遵守同源策略。 
   credentials: 请求的 credentials,如 omit、same-origin 或者 include。
        -   omit:默认值,忽略 cookie 的发送;
        -   same-origin:表示 cookie 只能同域发送,不能跨域发送;
        -   include:cookie 既可以同域发送,也可以跨域发送。
   cache: 请求的 cache 模式: default, no-store, reload, no-cache, force-cache, or only-if-cached. 
   redirect:可用的 redirect 模式: follow (自动重定向),error (如果产生重定向将自动终止并且抛出一个错误),或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
   referrer:一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
   referrerPolicy:指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、no-referrer-when-downgrade、origin、origin-when-cross-origin、unsafe-url 。
   integrity:包括请求的  subresource integrity 值

返回值:一个 Promise,resolve 时回传 Response 对象。
*/ fetch(input, init).then(function(response) { });

如下面例子:

// Example POST method implementation:
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

fetch 方法的返回值

fetch 方法,总是返回一个包含响应结果的 Promise 对象。当该 Promise 对象为 resolve 状态时,在其回调函数中可获取 Response 对象。

Response 的可配置参数包括:
type:类型,支持:basic,cors。
url:请求地址。
useFinalURL:Boolean 值,代表 url 是否是最终 URL。
status:状态码 (例如:200,404等等)。
ok:Boolean值,代表成功响应(status 值在 200-299 之间)。
statusText:状态值(例如:OK)。
headers:与响应相关联的 Headers 对象\

Response 提供的方法如下:
clone():创建一个新的 Response 克隆对象。
error():返回一个新的,与网络错误相关的 Response 对象。
redirect():重定向,使用新的 URL 创建新的 response 对象。
arrayBuffer():返回一个 promise,resolves 是一个 ArrayBuffer。
blob():返回一个 promise,resolves 是一个 Blob。
formData():返回一个 promise,resolves 是一个 FormData 对象。
json():返回一个 promise,resolves 是一个 JSON 对象。
text():返回一个 promise,resolves 是一个 USVString (text)。\

检测 fetch() 请求是否成功

如果遇到网络故障或服务端的 CORS 配置错误时,fetch() promise 将会 reject,带上一个 TypeError 对象。虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是否为 true。类似以下代码:

fetch('flowers.jpg')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not OK');
    }
    return response.blob();
  })
  .then(myBlob => {
    myImage.src = URL.createObjectURL(myBlob);
  })
  .catch(error => {
    console.error('There has been a problem with your fetch operation:', error);
  });

自定义请求对象

除了传给 fetch() 一个资源的地址,你还可以通过使用 Request() 构造函数来创建一个 request 对象,然后再作为参数传给 fetch()

const myHeaders = new Headers();

const myRequest = new Request('flowers.jpg', {
  method: 'GET',
  headers: myHeaders,
  mode: 'cors',
  cache: 'default',
});

fetch(myRequest)
  .then(response => response.blob())
  .then(myBlob => {
    myImage.src = URL.createObjectURL(myBlob);
  });

Request() 和 fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:

const anotherRequest = new Request(myRequest, myInit);

这个很有用,因为 request 和 response bodies 只能被使用一次(译者注:这里的意思是因为设计成了 stream 的方式,所以它们只能被读取一次)。创建一个拷贝就可以再次使用 request/response 了,当然也可以使用不同的 init 参数。创建拷贝必须在读取 body 之前进行,而且读取拷贝的 body 也会将原始请求的 body 标记为已读。

备注:  clone() 方法也可以用于创建一个拷贝。它和上述方法一样,如果 request 或 response 的 body 已经被读取过,那么将执行失败。区别在于, clone() 出的 body 被读取不会导致原 body 被标记为已读取。

Headers

使用 [Headers]的接口,你可以通过 Headers() 构造函数来创建一个你自己的 headers 对象。一个 headers 对象是一个简单的多键值对:

const content = 'Hello World';
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Length', content.length.toString());
myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');

也可以传入一个多维数组或者对象字面量:

const myHeaders = new Headers({
  'Content-Type': 'text/plain',
  'Content-Length': content.length.toString(),
  'X-Custom-Header': 'ProcessThisImmediately'
});

它的内容可以被获取:

console.log(myHeaders.has('Content-Type')); // true
console.log(myHeaders.has('Set-Cookie')); // false
myHeaders.set('Content-Type', 'text/html');
myHeaders.append('X-Custom-Header', 'AnotherValue');

console.log(myHeaders.get('Content-Length')); // 11
console.log(myHeaders.get('X-Custom-Header')); // ['ProcessThisImmediately', 'AnotherValue']

myHeaders.delete('X-Custom-Header');
console.log(myHeaders.get('X-Custom-Header')); // null

最好在在使用之前检查内容类型 content-type 是否正确,比如:

fetch(myRequest)
  .then(response => {
     const contentType = response.headers.get('content-type');
     if (!contentType || !contentType.includes('application/json')) {
       throw new TypeError("Oops, we haven't got JSON!");
     }
     return response.json();
  })
  .then(data => {
      /* process your data further */
  })
  .catch(error => console.error(error));

Headers 对象提供了以下方法,用来操作标头。

Headers.get():根据指定的键名,返回键值。
Headers.has(): 返回一个布尔值,表示是否包含某个标头。
Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加。
Headers.append():添加标头。
Headers.delete():删除标头。
Headers.keys():返回一个遍历器,可以依次遍历所有键名。
Headers.values():返回一个遍历器,可以依次遍历所有键值。
Headers.entries():返回一个遍历器,可以依次遍历所有键值对([key, value])。
Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数。\

上面的有些方法可以修改标头,那是因为继承自 Headers 接口。对于 HTTP 回应来说,修改标头意义不大,况且很多标头是只读的,浏览器不允许修改。

这些方法中,最常用的是response.headers.get(),用于读取某个标头的值。

let response = await fetch(url);

response.headers.get('Content-Type')
// application/json; charset=utf-8
Headers.keys()和Headers.values()方法用来分别遍历标头的键名和键值。

// 键名
for(let key of myHeaders.keys()) {undefined
    console.log(key);
}

// 键值
for(let value of myHeaders.values()) {undefined
    console.log(value);
}

//Headers.forEach()方法也可以遍历所有的键值和键名。
let response = await fetch(url);

response.headers.forEach(
    (value, key) => console.log(key, ':', value)
);

[Guard]

由于 Headers 可以在 request 中被发送或者在 response 中被接收,并且规定了哪些参数是可写的,Headers 对象有一个特殊的 guard 属性。这个属性没有暴露给 Web,但是它影响到哪些内容可以在 Headers 对象中被操作。

可能的值如下:

  • none:默认的。
  • request:从 request 中获得的 headers(Request.headers)只读。
  • request-no-cors:从不同域(Request.mode no-cors)的 request 中获得的 headers 只读。
  • response:从 response 中获得的 headers(Response.headers)只读。
  • immutable:在 ServiceWorkers 中最常用的,所有的 headers 都只读。

fetch 的应用

1.上传 JSON 数据

var url = 'https://example.com/profile';
var data = {username: 'example'};
 
fetch(url, {
  method: 'POST', // 或者 'PUT'
  body: JSON.stringify(data), // 数据可以是“string”或{object}!
  headers: new Headers({
    'Content-Type': 'application/json'
  })
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

2、上传文件

可以通过 HTML 元素,FormData() 和 fetch() 上传文件。

var formData = new FormData();
var fileField = document.querySelector("input[type='file']");
 
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
 
fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

3、上传多个文件

可以通过HTML 元素,FormData() 和 fetch() 上传文件。

var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");
 
formData.append('title', 'My Vegas Vacation');
// formData 只接受文件、Blob 或字符串,不能直接传递数组,所以必须循环嵌入
for (let i = 0; i < photos.files.length; i++) { 
    formData.append('photo', photos.files[i]); 
}
 
fetch('https://example.com/posts', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));

fetch 的实现

export default async(url = '', data = {}, type = 'GET', method = 'fetch') => {
    type = type.toUpperCase();
    url = baseUrl + url;
 
    if (type == 'GET') {
        let dataStr = ''; //数据拼接字符串
        Object.keys(data).forEach(key => {
            dataStr += key + '=' + data[key] + '&';
        })
 
        if (dataStr !== '') {
            dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
            url = url + '?' + dataStr;
        }
    }
 
    if (window.fetch && method == 'fetch') {
        let requestConfig = {
            credentials: 'include',//为了在当前域名内自动发送 cookie , 必须提供这个选项
            method: type,
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            mode: "cors",//请求的模式
            cache: "force-cache"
        }
 
        if (type == 'POST') {
            Object.defineProperty(requestConfig, 'body', {
                value: JSON.stringify(data)
            })
        }
        
        try {
            const response = await fetch(url, requestConfig);
            const responseJson = await response.json();
            return responseJson
        } catch (error) {
            throw new Error(error)
        }
    } else {
        return new Promise((resolve, reject) => {
            let requestObj;
            if (window.XMLHttpRequest) {
                requestObj = new XMLHttpRequest();
            } else {
                requestObj = new ActiveXObject;
            }
 
            let sendData = '';
            if (type == 'POST') {
                sendData = JSON.stringify(data);
            }
 
            requestObj.open(type, url, true);
            requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            requestObj.send(sendData);
 
            requestObj.onreadystatechange = () => {
                if (requestObj.readyState == 4) {
                    if (requestObj.status == 200) {
                        let obj = requestObj.response
                        if (typeof obj !== 'object') {
                            obj = JSON.parse(obj);
                        }
                        resolve(obj)
                    } else {
                        reject(requestObj)
                    }
                }
            }
        })
    }
}

fetch 的问题的解决

1、解决 fetch 的兼容问题 支持 fetch 的浏览器版本有:Chrome、Firefox、Safari 6.1+ 和 IE 10+。

虽然,不是所有的浏览器都支持 fetch 请求,但是我们可以用window.fetch polyfill来处理兼容问题

2、解决 fetch 不支持 timeout 处理的问题 fetch 的 timeout 的特点:

  • timeout 不是请求连接超时的含义,它表示请求的 response 时间(包括请求的连接、服务器处理 和 服务器响应回来的时间)。
  • fetch 的 timeout 即使超时发生了,本次请求也不会被丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已。

(1)、通过手动控制 promise 状态的实例来实现 fetch 的 timeout 功能
实现 fetch 的 timeout 功能,其思想就是新创建一个可以手动控制promise状态的实例,根据不同情况来对新promise实例进行resolve或者reject,从而达到实现timeout的功能。

var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
    return new Promise(function(resolve, reject){
        var timeoutId = setTimeout(function(){
            reject(new Error("fetch timeout"))
        }, opts.timeout);
        oldFetchfn(input, opts).then(
            res=>{
                clearTimeout(timeoutId);
                resolve(res)
            },
            err=>{
                clearTimeout(timeoutId);
                reject(err)
            }
        )
    })
}

(2)、利用 Promise.race 方法代替实现 fetch 的 timeout 的功能

var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
    var fetchPromise = oldFetchfn(input, opts);
    var timeoutPromise = new Promise(function(resolve, reject){
        setTimeout(()=>{
             reject(new Error("fetch timeout"))
        }, opts.timeout)
    });
    retrun Promise.race([fetchPromise, timeoutPromise])
}

3、解决 fetch 不支持进度事件(Progress Event) Progress Events定义了与客户端服务器通信有关的事件。这些事件最早其实只针对XHR操作,但目前也被其它API借鉴。有以下6个进度事件:

loadstart:在接收到相应数据的第一个字节时触发。 progress:在接收相应期间周期性地持续不断地触发。 error:在请求发生错误时触发。 abort:在因为调用abort()方法而终止链接时触发。 load:在接收到完整的相应数据时触发。 loadend:在通信完成或者触发error、abort或load事件后触发。

Ajax 的 XHR 是原生支持 progress 事件的,比如:

var xhr = new XMLHttpRequest()
xhr.open('POST', '/uploads')
xhr.onload = function() {}
xhr.onerror = function() {}
function updateProgress (event) {
  if (event.lengthComputable) {
    var percent = Math.round((event.loaded / event.total) * 100)
    console.log(percent)
  }
xhr.upload.onprogress =updateProgress; //上传的progress事件
xhr.onprogress = updateProgress; //下载的progress事件
}
xhr.send();

但 fetch 就不支持该事件。不过,fetch 内部设计实现了 Request 和 Response 类。其中 Response 封装一些方法和属性,通过 Response 实例可以访问这些方法和属性,例如 response.json()、response.body 等等。

response.body是一个可读字节流对象,其实现了一个getRender()方法,其具体作用是:用于读取响应的原始字节流,该字节流是可以循环读取的,直至body内容传输完成。因此,利用到这点可以模拟出 fetch 的 progress。

** 利用 response.body 模拟实现 fetch 的 progress 事件**

// fetch() returns a promise that resolves once headers have been received
fetch(url).then(response => {
  // response.body is a readable stream.
  // Calling getReader() gives us exclusive access to the stream's content
  var reader = response.body.getReader();
  var bytesReceived = 0;
 
  // read() returns a promise that resolves when a value has been received
  reader.read().then(function processResult(result) {
    // Result objects contain two properties:
    // done  - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (result.done) {
      console.log("Fetch complete");
      return;
    }
 
    // result.value for fetch streams is a Uint8Array
    bytesReceived += result.value.length;
    console.log('Received', bytesReceived, 'bytes of data so far');
 
    // Read some more, and call this function again
    return reader.read().then(processResult);
  });
});

Fetch 常见坑

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

Fetch 和标准 Promise 的不足

由于 Fetch 是典型的异步场景,所以大部分遇到的问题不是 Fetch 的,其实是 Promise 的。ES6 的 Promise 是基于 Promises/A+ 标准,为了保持 简单简洁 ,只提供极简的几个 API。如果你用过一些牛 X 的异步库,如 jQuery(不要笑) 、Q.js 或者 RSVP.js,可能会感觉 Promise 功能太少了。

没有 Deferred

Deferred 可以在创建 Promise 时可以减少一层嵌套,还有就是跨方法使用时很方便。 ECMAScript 11 年就有过 Deferred 提案,但后来没被接受。其实用 Promise 不到十行代码就能实现 Deferred:es6-deferred。现在有了 async/await,generator/yield 后,deferred 就没有使用价值了。

没有获取状态方法:isRejected,isResolved

标准 Promise 没有提供获取当前状态 rejected 或者 resolved 的方法。只允许外部传入成功或失败后的回调。我认为这其实是优点,这是一种声明式的接口,更简单。

缺少其它一些方法:always,progress,finally

always 可以通过在 then 和 catch 里重复调用方法实现。finally 也类似。progress 这种进度通知的功能还没有用过,暂不知道如何替代。

不能中断,没有 abort、terminate、onTimeout 或 cancel 方法

Fetch 和 Promise 一样,一旦发起,不能中断,也不会超时,只能等待被 resolve 或 reject。幸运的是,whatwg 目前正在尝试解决这个问题 whatwg/fetch#27

参考:
WHATWG Fetch 规范 developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/…
www.w3cschool.cn/fetch_api/