js ES6 fetch 方法

戚翼
2023-12-01

目录

一、fetch 概述

二、fetch 的语法

1、实现一个简单的 fetch 请求

2、fetch 方法介绍

(1)、fetch 方法的第一个参数

(2)、fetch 方法的第二个参数

(3)、fetch 方法的返回值

3、检测 fetch() 请求是否成功

三、fetch 的应用

1、上传 JSON 数据

2、上传文件

3、上传多个文件

四、fetch 的实现

五、fetch 的问题的解决

1、解决 fetch 的兼容问题

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

(1)、通过手动控制 promise 状态的实例来实现 fetch 的 timeout 功能

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

3、解决 fetch 不支持进度事件(Progress Event)

(1)、利用 response.body 模拟实现 fetch 的 progress 事件

(2)、使用 Promise+XHR 结合的方式实现类 fetch 的 progress 效果

4、解决 fetch 不支持 JSONP 跨域的问题

5、解决 fetch 的跨域问题

六、深入学习 fetch 的资源


一、fetch 概述

fetch 是一种 HTTP 数据请求的方式,它不是 ajax 的进一步封装,而是 XMLHttpRequest(以下简称 XHR)的一种替代方案。

fetch 与 ajax 的区别:

  • fetch() 方法是原生的 JavaScript 方法,可以直接使用,而 ajax 需要二次封装成一个方法才更便于使用。
  • fetch() 方法会返回的一个 promise 对象,它不会拒绝 http 的错误状态,即使响应是一个HTTP 404 或 500,Promise 状态也会被标记为 resolve,但是会将 resolve 的返回值的 ok 属性设置为 false。当仅当网络故障时或请求被阻止时,才会标记为 reject。而 ajax,默认返回的是一个普通对象。
  • 在默认情况下,fetch() 方法不会接受或者发送 cookies。只有手动设置 credentials 为 include 时,才可以发送 cookies。而 ajax,允许接收和发送 cookies。
  • fetch() 方法不支持超时(timeout)处理。而 ajax,支持超时处理。

 

二、fetch 的语法

1、实现一个简单的 fetch 请求

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

2、fetch 方法介绍

fetch 方法接受两个参数:一个 URL 地址或一个 request 对象 和 (可选的)一个配置项对象。

(1)、fetch 方法的第一个参数

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

var myHeaders = new Headers();

var myInit = { method: 'GET',
               headers: myHeaders,
               mode: 'cors',
               cache: 'default' };

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

(2)、fetch 方法的第二个参数

(可选的)一个配置项对象。该配置项包括所有对请求的设置。

配置项可选的参数有:

  • method:请求使用的方法,可选的值有 GET、POST、 PUT、 DELETE、OPTION、HEAD等。
  • headers:请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
  • body:请求的 body 信息——可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。必须与'Content-Type'标头匹配。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
  • mode:请求的模式,可选的值有 cors、no-cors 或者 same-origin。
  • credentials:请求的资格证书,可选的值有 omit、same-origin 或者 include。
    • omit:默认值,忽略 cookie 的发送;
    • same-origin:表示 cookie 只能同域发送,不能跨域发送;
    • include:cookie 既可以同域发送,也可以跨域发送。
  • cache:  请求的 cache 模式 default、no-store、reload、no-cache、force-cache 或者 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 值( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

举个栗子:

// POST方法实现示例
postData('http://example.com/answer', {answer: 42})
  .then(data => console.log(data)) 
  .catch(error => console.error(error))

function postData(url, data) {
  // 默认值标记为 *
  return fetch(url, {
    body: JSON.stringify(data), // 必须与'Content-Type'标头匹配
    cache: 'no-cache', // 可选的值有*default, no-cache, reload, force-cache, only-if-cached。
    credentials: 'same-origin', // 可选的值有include, same-origin, *omit
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    },
    method: 'POST', // 可选的值有*GET, POST, PUT, DELETE等。
    mode: 'cors', // 可选的值有no-cors, cors, *same-origin
    redirect: 'follow', // 可选的值有manual, *follow, error
    referrer: 'no-referrer', // 可选的值有*client, no-referrer
  })
  .then(response => response.json()) // 解析JSON响应
}

(3)、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)。

3、检测 fetch() 请求是否成功

当 fetch 方法返回的 promise 对象的状态是 resolved 时,调用 then 方法,在 then 方法的回调函数中判断 response.ok 为 true 时,才表示 fetch() 请求是成功的。否则,fetch() 请求就是失败的。

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

 

三、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 <input type="file" /> 元素,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 <input type="file" mutiple/> 元素,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。

(1)、利用 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);
  });
});

(2)、使用 Promise+XHR 结合的方式实现类 fetch 的 progress 效果

function fetchProgress(url, opts={}, onProgress){
    return new Promise(funciton(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.open(opts.method || 'get', url);
        for(var key in opts.headers || {}){
            xhr.setRequestHeader(key, opts.headers[key]);
        }

        xhr.onload = e => resolve(e.target.responseText)
        xhr.onerror = reject;
        if (xhr.upload && onProgress){
            xhr.upload.onprogress = onProgress; //上传
        }
        if ('onprogerss' in xhr && onProgress){
            xhr.onprogress = onProgress; //下载
        }
        xhr.send(opts.body)
    })
}
fetchProgress('/upload').then(console.log)

4、解决 fetch 不支持 JSONP 跨域的问题

JSONP 是外链一个javascript资源。

如何基于Promise来实现一个JSONP,并且使其看起来就像 fetch 支持 JSONP 一样?

可以使用 fetch-jsonp 插件。使用演示如下:

首先需要用 npm 安装 fetch-jsonp:

 npm install fetch-jsonp --save-dev

然后在像下面一样使用:

fetchJsonp('/users.jsonp', {
    timeout: 3000,
    jsonpCallback: 'custom_callback'
  })
  .then(function(response) {
    return response.json()
  }).catch(function(ex) {
    console.log('parsing failed', ex)
  })

5、解决 fetch 的跨域问题

XHR 2 级 支持一种跨域:

  • XHR 2 级是支持跨域请求的,只不过要满足浏览器端支持 CORS,服务器通过 Access-Control-Allow-Origin 来允许指定的源进行跨域,仅此一种方式。

fetch 支持两种跨域请求:

  • 最常用:与 XHR 2 级一样,支持跨域请求需要满足两个条件:浏览器端支持 CORS 和 服务器通过 Access-Control-Allow-Origin 来允许指定的源进行跨域。
  • 特殊:fetch 还支持一种跨域,不需要服务器支持的形式,具体可以通过其 mode 的配置项来实现。fetch 的 mode 配置项有3个值,如下:
    • same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个error告知不能跨域;其对应的response type为basic。
    • cors(最常用):该模式支持跨域请求,顾名思义它是以CORS的形式跨域;当然该模式也可以同域请求不需要后端额外的CORS支持;其对应的response type为cors。
    • no-cors(特殊)该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS;这也是fetch的特殊跨域请求方式;其对应的response type为opaque。(该模式允许浏览器发送本次跨域请求,但是不能访问响应返回的内容,这也是其response type为opaque透明的原因。)

总的来说,fetch 的跨域请求是使用 CORS 方式,需要浏览器和服务端的支持。

 

六、深入学习 fetch 的资源

Js中fetch方法:https://www.cnblogs.com/WindrunnerMax/p/13024711.html

传统 Ajax 已死,Fetch 永生:https://segmentfault.com/a/1190000003810652

XHR or Fetch API ?:http://jartto.wang/2017/01/17/xhr-or-fetch-api/

res.json() 与 res.send() 的区别?:https://andyli.blog.csdn.net/article/details/79753342

 类似资料: