Javascript学习笔记 - Fetch API

917 阅读6分钟

看着2019年过半,立的flag还剩好多,所以尽量之后能2周一篇的速度来敢一下进度,完成今年的计划。

一、Fetch API

Fetch API目前各浏览器的支持情况较好,和XHR实现了相同的数据请求,在返回结果处理上,使用了Promise,使其结果可以使用Promise的链式写法

1. Fetch

相比于XHR的创建过程,

var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onreadystatechange = function(e) {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText)
    }
}
xhr.send(data);

Fetch的使用步骤就简单很多:

fetch(url[,init]).then(res => {
    return res.json();
}).then(data => {
    console.log(data);
});

Fetch对象接受两个参数,第一个参数可以是一个url字符串,也可以是一个Request对象;第二个参数接受一些初始化的options(在Request中详细说明)

fetch(url[,init])
fetch(new Request()[,init])

Fetch返回的结果为一个Promiseresolve值是一个Response对象,有两个注意的地方:

Fetch请求结果即使http请求响应码为4xx和5xx的请求,也不会进行reject,除非网络请求失败或者请求受到阻碍

// node.js代码
http.createServer((req, res) => {
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.writeHead(404)
    res.end('fail')
}).listen(9000)

// fetch
fetch('http://localhost:9000').then(res => {
    console.log(res.text()) // 输出fail
}, err => {
    console.log(err)
})

默认Fetch在跨域的时候不会携带cookie进行请求,如果需要携带cookie,需要设置credentialsincludeXHR是设置withCredentials=true),同时服务器需要配置Access-Control-Allow-Credentials: true

// node跨域配置
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080')
res.setHeader('Access-Control-Allow-Credentials', 'true')

// fetch配置
fetch('http://localhost:9000', {
    credentials: 'include'
})

2. Request

Request可以作为Fetch的参数,通过new Request(input[, init])构建一个完整的请求对象,input可以是一个url,也可以是一个复制的Request对象

let request = new Request('http://localhost:9000');
fetch(request);
// 接受一个request
let request2 = new Request(request.clone())
fetch(request2);

Request可以设置多个初始化参数:

2.1 method

请求的请求方式:getpostdelete

2.2 headers

请求头信息,可以是普通的对象字面量,也可以Headers对象

2.3 body

请求携带的数据对象,如果methodgethead会忽略掉该设置

2.4 mode

用于决定跨域请求的响应信息是否有效,值包括:

'same-origin':只允许同源请求,否则会提示错误。

'no-cors':只能使用HEADGETPOST请求,同时Headers必须是simple headers的请求

'cors':允许跨域

2.5 credentials

用于决定cookie的传递方式,默认跨域情况下是请求是不会传递cookie的,可以设置的值包括:omit(不携带cookie),same-origin(默认值,同源携带cookie),include(携带cookie

2.6 cache

用于设置浏览器对于请求结果的缓存机制,值包括:

default: 如果匹配或者fresh则从缓存获取,否则从远端获取

no-store: 不会检查cache,优先请求服务器,且本地不会更新cache

reload: 不会检查cache,优先请求服务器,但结果会更新cache

no-cache: 浏览器会发送conditional request请求服务器,判断cache是否更新,如果未更新,使用本地cache,否则从服务器重新获取数据,并更新本地cache

force-cache: 优先从cache获取,如果未找到匹配,浏览器发送普通请求,获取数据并进行cache

only-if-cached: 只从cache获取,如果未找到匹配,则返回504 Gateway timeout,只适用于mode设置为same-origin的情况

3. Headers

Headers对象用于替代字面量对象的方式,給RequestResponse设置请求/响应的头信息,相比于字面量对象的方式,提供了较多的方法来处理headers

3.1 headers.set()

设置头信息中属性,如果已经存在该头信息,则会对其进行覆盖

let headers = new Headers();
headers.set('Content-Type', 'application/json');
console.log(headers.get('Content-Type')); // application/json
headers.set('Content-Type', 'plain/text');
console.log(headers.get('Content-Type')); // plain/text

3.2 headers.append()

用于追加头信息中属性,如果已经存在该信息,则会对其进行追加

let headers = new Headers();
headers.append('Content-Type', 'application/json');
console.log(headers.get('Content-Type')); // application/json
headers.append('Content-Type', 'plain/text');
console.log(headers.get('Content-Type')); // application/json, plain/text

3.3 headers.delete()

用于删除头信息中属性

let headers = new Headers();
headers.append('Content-Type', 'application/json');
console.log(headers.get('Content-Type')); // application/json
headers.delete('Content-Type');
console.log(headers.get('Content-Type')); // null

3.4 headers.get()

用于获取设置的头信息中属性的值

3.5 headers.has()

用于判断头信息中是否已经设置属性

let headers = new Headers();
console.log(headers.has('Content-Type')); // false
headers.append('Content-Type', 'application/json');
console.log(headers.has('Content-Type')); // true

3.6 values, keys, entries

用于遍历所有的headers的属性,都会返回一个iterator对象

let headers = new Headers();
headers.append('Content-Type', 'application/json');
for(let key of headers.keys()) {
    console.log(key); // content-type
}
for(let val of headers.values()) {
    console.log(val); // application/json
}
for(let entry of headers.entries()) {
    console.log(entry); // ['content-type', 'application/json'}]
}

PS:headers值的有效性受到Request的mode值的影响。

4. Response

响应体对象,Fetch返回Promiseresolve的值,也可以自行构建Response对象(暂时没发现直接构建使用的用途),可以通过

new Response(body[,init])

的方式创建

body是传递的参数,可以是各种数据类型: BufferSource, Blob, FormData, ReadableStreamURLSearchParams

init主要包括status, statusText, headers等,其他几个常用

Response.ok: 如果响应码在(200-299)返回true

Response.status: 返回请求响应码,例如:200

Response.statusText: 返回请求响应码文本,例如:200的响应码文本默认是'OK'

5. Body

Body主要是Response请求的结果,默认是ReadableStream类型,Response实现了json(), text(), arrayBuffer(), blob(), formData()方法,可以直接将body转化为对应数据类型

fetch('http://localhost:9000').then(res => res.json()) // 返回一个json类型数据

6. Abort

如何取消一个Fetch的请求呢?现代浏览器提供了AbortControl的类来处理,在Fetch的时候,初始化参数可以接收一个signal的参数,从而在AbortControl出发abort的时候,抛出异常终止Fetch,详细可以参见参考中MDN上的相关说明

let controller = new AbortController();
let signal = controller.signal;
fetch(url, {signal}).then(res => {
}).catch(e => {
    console.log(e);
})
controller.abort();

二、示例

一个简单的的关于Fetch API使用的例子

// index.js - node
let http = require('http');
let fs = require('fs')

let server = http.createServer((req, res) => {
    // 设置跨域
    // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080')
    // 设置是否跨域cookie
    // res.setHeader('Access-Control-Allow-Credentials', 'true')
    // 返回文本类型响应
    if (req.url.indexOf('/text') > -1) {
        res.setHeader('Content-Type', 'plain/text')
        res.end('test')
    }
    // 返回json类型响应
    if (req.url.indexOf('/json') > -1) {
        res.setHeader('Content-Type', 'application/json')
        res.end(JSON.stringify({text: 'test'}))
    }
    // 返回文件数据流
    if (req.url.indexOf('/file/') > -1) {
        // 通过截取后缀判断文件并返回文件内容作为请求输出
        let fileName = req.url.substr(req.url.indexOf('/file/') + 6, req.url.length);
        fs.stat(fileName, function (err, stats) {
            if (!err && stats.isFile()) {
                // 没有出错并且文件存在:
                console.log('200 ' + req.url);
                // 发送200响应:
                res.writeHead(200);
                // 将文件流导向response:
                fs.createReadStream(fileName).pipe(res);
            } else {
                // 出错了或者文件不存在:
                console.log('404 ' + req.url);
                // 发送404响应:
                res.writeHead(404);
                res.end('404 Not Found');
            }
        });
    }
});
server.listen(9000)

// test.js - client

// 使用Request创建一个请求
let req = new Request('http://localhost:9000/text')
fetch(req, {
  credentials: 'omit', // 设置不传递cookie
  mode: 'no-cors', // 设置请求不允许跨域
}).then(res => {
  return res.text();
}).then(data => {
  console.log(data)
})

三、总结

简单学习了Fetch API相关的内容,如果习惯使用Promise带来的链式编程或者习惯使用async/awaitFetch API相比于使用XHR有其特有优势,不过现在大家都习惯于直接使用第三方库(例如:axios)做数据请求,所以目前直接应用场景似乎不多

但是Fetch也有自身局限性,比如:XHR可以监听文件上传进度,但是目前Fetch API并没有提供相关功能。

四、参考

MDN Fetch API

fetch使用的常见问题及解决办法

使用AbortControl来取消Fetch