您的当前位置:首页正文

什么是Fetch

2024-11-25 来源:个人技术集锦

Ajax与Fetch

JavaScript 通过XMLHttpRequest(XHR)来执行异步请求,这个方式已经存在了很长一段时间。虽说它很有用,但它不是最佳API。
它在设计上不符合职责分离原则,将输入、输出和用事件来跟踪的状态混杂在一个对象里。
而且,基于事件的模型与最近JavaScript流行的Promise以及基于生成器的异步编程模型不太搭(事件模型在处理异步上有点过时了)。

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState === 4 && this.status === 200) {
      console.log(this.responseText);
    }
};
xhttp.open("GET", "/", true);
xhr.onload = function() {
  console.log(xhr.response);
};

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

而新的 Fetch API打算修正上面提到的那些缺陷。
Fetch API 提供了能够用于操作一部分 HTTP 的 JavaScript 接口,比如 requests 和 responses。它同时也提供了一个全局的 fetch() 方法——能够简单的异步的获取资源。

Fetch接口

Fetch 提供了对 Request 和 Response 等对象通用的定义。
发送请求或者获取资源,需要使用 fetch() 方法。

fetch()

Fetch API 中的GlobalFetch 接口包含了GlobalFetch.fetch() 方法,它被用于发送请求获取资源。这个方法返回一个promise,这个promise会在请求响应后被resolve,并传回Response对象。
当遇到网络错误时,fetch返回的promise会被reject,并传回TypeError。

// url (必须), options (可选)
fetch('/some/url', {
    method: 'get'
}).then(function(response) {

}).catch(function(err) {
    // 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(
});

fetch API 也使用了 JavaScript Promises 来处理结果/回调:

// 链式处理,将异步变为类似单线程的写法: 高级用法.
fetch('/some/url').then(function(response) {
    return //... 执行成功, 第1步...
}).then(function(returnedValue) {
    // ... 执行成功, 第2步...
}).catch(function(err) {
    // 中途任何地方出错...在此处理 :( 
});

Headers

Fetch API 的Headers类允许你去对HTTP request和response headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。
一个Headers类里包含一个header列表, 它的初始值为空或者是零个或多个键值对。

下面是一个示例:

// 创建一个空的 Headers 对象,注意是Headers,不是Header
var headers = new Headers();

// 添加(append)请求头信息
headers.append('Content-Type', 'text/plain');
headers.append('X-My-Custom-Header', 'CustomValue');

// 判断(has), 获取(get), 以及修改(set)请求头的值
headers.has('Content-Type'); // true
headers.get('Content-Type'); // "text/plain"
headers.set('Content-Type', 'application/json');

// 删除某条请求头信息(a header)
headers.delete('X-My-Custom-Header');

// 创建对象时设置初始化信息
var headers = new Headers({
    'Content-Type': 'text/plain',
    'X-My-Custom-Header': 'CustomValue'
});

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

一般来说,需要创建一个 Request 对象来包装请求头:

var request = new Request('/some-url', {
    headers: new Headers({
        'Content-Type': 'text/plain'
    })
});

fetch(request).then(function() { /* handle response */ });

Request

Request 对象表示一次 fetch 调用的请求信息。
可以使用构造函数Request.Request()来创建一个新的Request对象。

var req = new Request("/index.html");
console.log(req.method); // "GET"
console.log(req.url); // "http://example.com/index.html"

你也可以将一个建好的Request对象传给构造函数,这样将复制出一个新的Request:

var copy = new Request(req);
console.log(copy.method); // "GET"
console.log(copy.url); // "http://example.com/index.html"

URL以外的其他属性的初始值能够通过第二个参数传给Request构造函数。这个参数是一个json:

var uploadReq = new Request("/uploadImage", {
  method: "POST",
  headers: {
    "Content-Type": "image/png",
  },
  body: "image data"
});

Request对象的mode属性用来决定是否允许跨域请求,以及哪些response属性可读。可选的mode属性值为same-origin,no-cors(默认)以及cors。

  • same-origin:模式很简单,如果一个请求是跨域的,那么返回一个简单的error,这样确保所有的请求遵守同源策略。
  • no-cors:允许来自CDN的脚本、其他域的图片和其他一些跨域资源,但是首先有个前提条件,就是请求的method只能是”HEAD”,”GET”或者”POST”。
  • cors:我们通常用作跨域请求来从第三方提供的API获取数据。这个模式遵守CORS协议。只有有限的一些headers被暴露给Response对象,但是body是可读的。

Request对象的credentials枚举属性决定了cookies是否能跨域得到。

Response

Fetch API 的Response接口呈现了对一次请求的响应数据。
你可以使用Response.Response() 构造函数来创建一个Response对象,但一般更可能遇到的情况是,其他的API操作返回了一个Response对象,例如一个简单的 GlobalFetch.fetch()。因此Response实例通常在fetch()的回调中获得。
Response有一系列的属性:

  • Response.type 只读:包含Response的类型 (例如, basic, cors).
  • Response.url 只读:包含Response的URL.
  • Response.useFinalURL:包含了一个布尔值来标示这是否是该Response的最终URL.
  • Response.status 只读:包含Response的状态码 (例如, 200 成功).
  • Response.ok 只读:包含了一个布尔值来标示该Response成功(状态码200-299) 还是失败.
  • Response.statusText 只读:包含了与该Response状态码一致的状态信息 (例如, OK对应200).
  • Response.headers 只读:包含此Response所关联的Headers 对象.

处理body

无论Request还是Response都可能带着body。我们可以对其中的body进行处理。
Request和Response都为他们的body提供了以下方法,这些方法都读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并且都返回一个Promise对象。

  • arrayBuffer():返回一个被解析为ArrayBuffer格式的promise对象
  • blob():返回一个被解析为Blob格式的promise对象
  • formData():返回一个被解析为FormData格式的promise对象
  • json():返回一个被解析为JSON格式的promise对象
  • text():返回一个被解析为USVString格式的promise对象

  • 处理json响应

fetch('https://davidwalsh.name/demo/arsenal.json').then(function(response) { 
    // 转换为 JSON
    return response.json();
}).then(function(j) {
    // 现在, `j` 是一个 JavaScript object
    console.log(j); 
});
  • 处理基本的Text / HTML响应
fetch('/next/page')
.then(function(response){
    return response.text();
})
.then(function(text){
     // <!DOCTYPE ....
    console.log(text); 
})
  • 处理Blob结果
    如果你想通过 fetch 加载图像或者其他二进制数据, 则会略有不同:
fetch('flowers.jpg')
    .then(function(response) {
      return response.blob();
    })
    .then(function(imageBlob) {
      document.querySelector('img').src = URL.createObjectURL(imageBlob);
    });
  • 另一种常用的调用是提交表单数据
fetch('/submit-json',{
    method:'post',
    body:JSON.stringify({
        email:document.getElementById('email').value,
        answer:document.getElementById('answer').value
    })
})

另外,非常重要的一点要说明,那就是Request和Response的body只能被读取一次!它们有一个属性叫bodyUsed,读取一次之后设置为true,就不能再读取了。

var res = new Response("one time use");
console.log(res.bodyUsed); // false
res.text().then(function(v) {
  console.log(res.bodyUsed); // true
});
console.log(res.bodyUsed); // true

res.text().catch(function(e) {
  console.log("Tried to read already consumed Response");
});

但有时候,我们希望多次访问body,API提供了一个clone()方法。调用这个方法可以得到一个克隆对象。不过要记得,clone()必须要在读取之前调用,也就是先clone()再读取。

var image1 = document.querySelector('.img1');
var image2 = document.querySelector('.img2');

var myRequest = new Request('flowers.jpg');

fetch(myRequest).then(function(response) {
  var response2 = response.clone();

  response.blob().then(function(myBlob) {
    var objectURL = URL.createObjectURL(myBlob);
    image1.src = objectURL;
  });

  response2.blob().then(function(myBlob) {
    var objectURL = URL.createObjectURL(myBlob);
    image2.src = objectURL;
  });
});

Fetch用法

浏览器支持情况

Fetch 的支持目前还处于早期的阶段,不过正在取得良好的进展。它目前在 Firefox 39 以上,和 Chrome 42 以上都被支持了。

如果你现在就想使用它,还可以用 ,用于支持那些还未支持 Fetch 的浏览器。

功能检测

Fetch API 的支持情况,可以通过检测 Headers、Request、Response 或 fetch() 是否在 Window 或 Worker 域中。

if(self.fetch) {
    // run my fetch request here
} else {
    // do something with XMLHttpRequest?
}

发起fetch请求

var myImage = document.querySelector("img");

fetch("flowers.jpg").then(function(response){
    return response.blob();
})
.then(function(myBlob){
    var objectURL = URL.createObjectURL(myBlob);
    myImage.src = objectURL;
})

自定义请求参数

fetch()接收第二个可选参数,一个可以控制不同配置的init对象。

var myHeaders = new Hearders();
vart myInit = {
    method:'get',
    headers:myHeaders,
    mode:'cors',
    cache:'default'
};

fetch('flowers,jpg',myInit)
.then(function(response){
    return response.blob();
})
.then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

检测请求是否成功

如果遇到网络故障,fetch() promise 将会 reject,带上一个 TypeError 对象。
想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是不是为 true。

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

自定义请求对象

var myHeaders = new Headers();

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

var myRequest = new Request('flowers.jpg',myInit);

fetch(myRequest,myInit)
.then(function(response) {
  return response.blob();
})
.then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

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

var anotherRequest = new Request(myRequest,myInit);

这个很有用,因为 request 和 response bodies 只能被使用一次(因为设计成了 stream 的方式,所以它们只能被读取一次)。创建一个拷贝就可以再次使用 request/response 了。

实例

在我们自己的项目里,现在都是应用ajax的:

function ajaxNode(page){
        $.get("nodeAdmin/ajaxNode",
            {
                page:page-1,
        },function(data){
            $("#nodeList").html(data);
        }) 

    }

试着更换成fetch:

function ajaxNode(page){
        fetch("nodeAdmin/ajaxNode",{page:page-1}).then(function(response){
            return response.text();
        }).then(function(data){
            $("#nodeList").html(data);
        })
    }

it works!而且确实发现Promise这种链式操作非常清晰。

缺点

  • 使用 fetch 无法取消一个请求。这是因为 Fetch API 基于 Promise,而 Promise 无法做到这一点。

由于 Fetch 是典型的异步场景,所以大部分遇到的问题不是 Fetch 的,其实是 Promise 的。

显示全文