6. React Native 网络请求

4,662 阅读5分钟

Fetch API提供了处理网络请求与响应的JavaScript接口。它提供了一个全局的fetch()函数以轻松合理地异步获取网络资源。

fetch请求的规则与jQuery.ajax()有两点不同:

  • fetch不会因为HTTP错误状态码而不返回Promise,如状态码为404/500等。它会正常解析请求(将ok状态置为否)。只有在网络错误或者某些因素阻止请求完成的情况下,fetch才会不返回Promise
  • fetch默认情况下不会发送和接收任何服务器返回的cookie,这会导致某些需要保持用户会话有效的站点产生未授权的请求错误。如需发送cookie,需在请求的初始化选项中设置credentials

最简单的fetch请求示例

// 发起网络请求
const response = await fetch('http://example.com/movie.json');
// 将响应对象解析为json	
const myJson = await response.json();
// 将json对象转换为string并输出到控制台
console.log(JSON.stringify(myJson));

提供请求参数

fetch()函数可以提供一个额外的init对象参数来配置网络请求的相关信息。

见代码

try {
	const data = await postData('http://example.com/answer', { 'answer': 42 });
	console.log(JSON.stringify(data));
} catch (error) {
    console.error(error);
}

async function postData(url = '', data = {}) {
    // 默认的options会被标记为 * 
    const response = await fetch(url, {
        method: 'POST', // 请求方法,GET/POST/PUT/DELETE等
        mode: 'cors',   // 跨域请求配置,no-cors/*cors/same-origin
        cache: 'no-cache',  // 缓存策略,*default/no-cache/reload/force-cache/only-if-cached
        credentials: 'same-origin', // 是否发送cookie,*same-origin/include/omit
        headers: {
            'Content-Type': 'application/json', // body内容类型,'application/x-www-form-urlencoded'
        }
        redirect: 'follow', // 重定向,*follow/manual/error
        referrer: 'no-referrer',    // 请求来源,no-referrer, *client
        body: JSON.stringify(data)  // body数据类型必须与headers中的`Content-Type`一致
    });
    
    return await response.json();
}

发送包含认证信息的请求

如需发送带有认证信息的请求,可以在init对象参数中添加credentials字段。

credential的三种取值:

  • include:总是在请求中包含cookie
  • same-origin:仅在请求同源地址时携带cookie
  • omit:在请求中忽略cookie

上传JSON数据

try {
    const response = await fetch(url, {
        method: 'POST', // or 'PUT'
        body: JSON.stringify(data), // data can be `string` or {object}!
        headers: {
            'Content-Type': 'application/json'
        }
    });
    const json = await response.json();
    console.log('Success:', JSON.stringify(json));
} catch (error) {
    console.error('Error:', error);
}

上传文件

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

try {
    const response = await fetch('https://example.com/profile/avatar', {
        method: 'PUT', 
        body: formData,
    });
    const result = await response.json();
    console.log('Success', JSON.stringify(result));
} catch (error) {
    console.error(error);
}

上传多个文件

const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');

formData.append('title', 'My Vegas Vacation');
for (let i = 0; i < photos.files.length; i++) {
    formData.append('photos', photos.files[i]);
}

try {
    const response = await fetch('https://example.com/posts', {
        method: 'POST',
        body: formData
    });
    const result = await response.json();
    console.log('Success:', JSON.stringify(result));
} catch (error) {
    console.error('Error:', error);
}

逐行处理文本

检查请求是否成功

精确检查fetch()请求是否成功,应先检查fetch()是否返回了Promise,再检查响应的状态码ok是否正常。例如

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

自定义请求对象参数

可以使用Request()构造器创建自定义的请求对象作为fetch()的参数,而无需传入uri路径。例如:

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

Request()接收的参数和fetch()完全一致,甚至可以传入一个已存在的request对象来创建它的副本。例如:

const anotherRequest = new Request(myRequest, myInit);

通过创建副本以便于根据需要改变init选项来重复利用request/response。拷贝必须在body被使用前完成,而且即使读取了副本请求而使用了body数据,也会使原请求中的body数据被标记为已使用。

注:还有另外一个clone()方法也可以创建副本。当原请求的body已使用时,两个方法都会失败。但是,读取副本请求不会使body对象在原请求中被标记为已使用。(someRequest.clone())


Headers

Headers接口允许通过Headers()初始化器创建自定义的请求头对象。此对象实际就是一个简单的Map

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');

操作请求头

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')); // [ ]

headers的一个经典使用场景,是检测Content-Type是否正确。例如:

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

Guard

可以设置headersguard属性以限制修改其属性内容。guard属性的取值类型有:

  • none:默认
  • request:禁止修改从请求中获取的请求头对象
  • request-no-cors:禁止修改从请求中获取的Request.modeno-cors的请求头
  • response:禁止修改从响应中获取的请求头
  • immutable:将请求头标记为只读

Response Object

最常用的响应属性:

  • Response.status:响应状态码,如200
  • Response.statusText:对应于HTTP状态码的状态文本。默认为ok
  • Response.ok:响应状态的便捷判断,当HTTP状态码在200~299之间时为true

React Native中的Fetch

发起网络请求

最简单的请求方法,直接将URL作为参数传给fetch调用即可。

fetch('https://mywebsite.com/mydata.json');

同理,可以传递init对象提供额外的请求参数。例如:

fetch('https://mywebsite.com/endpoint/', {
    method: 'POST',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        firstParam: 'yourValue',
        secondParam: 'yourOtherValue',
    })
})

处理服务器响应数据

fetch()会返回一个Promise对象`,因此可以简化异步代码编写风格。例如:

function getMoviesFromApiAsync() {
    return fetch('https://facebook.github.io/react-native/movies.json', {
        method: 'GET',
        cache: 'no-cache'
    })
    .then((response) => response.json())
    .then((responseJson) => {
        return responseJson;
    })
    .catch((error) => {
        console.error(error);
    });
}

也可以使用ES2017的async/await语法这样写:

async function getMoviesFromApi() {
    try {
        let response = await fetch('https://facebook.github.io/react-native/movies.json');
        let responseJson = await response.json();
        return responseJson;
    } catch (error) {
        console.error(error);
    }
}

注意必须在末尾处处理错误,否则程序可能会发生异常。

利用Promise来封装网络请求组件

class NetworkUtils {
    static get(url) {
        return new Promise((resolve, reject) => {
            fetch(url)
            .then(response => response.json())
            .then(result => resolve(result))
            .catch(error => reject(error))
        })
    }

    static post(url, params) {
        return new Promise((resolve, reject) => {
            fetch(url, {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(params)
            })
            .then(response => response.json())
            .then(result => resolve(result))
            .catch(error => reject(error))
        })
    }
}