使用 Fetch 创建一个很棒的 JS API 接口(少于 50 行)
在本教程中,我们将创建一个可复用的模块,用不到 50 行代码即可完成所有 API 调用! (点击此处查看最终模块代码)。如果您熟悉 fetch API,您就会明白,由于使用了各种 Promise 链,它看起来会变得多么丑陋和难以理解。
fetch(url)
.then((response) => response.json())
.then(data => {
console.dir(data);
});
})
呃。你肯定不想在应用中每次 API 调用时都这么做。我们应该把它抽象成一个模块,这样你的调用会更清晰、更容易。这样 API 调用就会像这样:
async function getArticles(userId) {
const articles = await Fetch.get('/articles/' + userId);
articles.forEach(item => {
// iterate through results here...
});
}
本教程将使用 ES6 模块。开始吧!(注意 async 和 await 关键字)。
创建 Fetch.js 模块骨架
首先,让我们定义用于与模块交互的公共 CRUD 方法并定义 API 主机:
// Fetch.js
const _apiHost = 'https://api.service.com/v1';
export default {
get,
create,
update,
remove
};
创建主获取方法
接下来,我们在模块内部创建一个私有方法来执行实际的获取操作。这个通用方法应该能够处理读取和写入 API 调用。该方法将接受 3 个参数:
- url - API 端点 - 例如:'/articles'
- params - 作为 GET 的查询字符串或 POST、UPDATE 和 DELETE 调用的请求主体数据传递给端点的附加参数
- 方法- GET(默认)、POST、PUT、DELETE
// Fetch.js
// ...
async function request(url, params, method = 'GET') {
}
// ...
注意函数开头的async 。我们需要它,因为我们要处理Promises。
添加选项并确保使用“await”
// Fetch.js
const _apiHost = 'https://api.service.com/v1';
async function request(url, params, method = 'GET') {
// options passed to the fetch request
const options = {
method
};
// fetch returns a promise, so we add keyword await to wait until the promise settles
const response = await fetch(_apiHost + url, options);
const result = await response.json(); // convert response into JSON
// returns a single Promise object
return result;
}
// ...
处理所有请求类型的参数
我们希望此方法能够处理 API 请求中可能需要包含的任何参数。对于GET请求,我们需要支持查询字符串。对于所有其他与写入相关的请求,我们需要能够在请求中包含正文。
我们希望我们的模块超级易用,因此我们将标准化所有情况下的对象处理方式。然后,我们可以将对象转换为GET请求的查询字符串,并将其作为 JSON 格式包含在所有其他类型的请求正文中。
让我们创建对象来查询字符串转换方法:
// Fetch.js
// ...
// converts an object into a query string
// ex: {authorId : 'abc123'} -> &authorId=abc123
function objectToQueryString(obj) {
return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}
// ...
现在我们可以添加一些代码来处理参数(如果存在)。让我们在请求方法中的选项定义之后添加这些代码:
// Fetch.js
const _apiHost = 'https://api.service.com/v1';
async function request(url, params, method = 'GET') {
const options = {
method,
headers: {
'Content-Type': 'application/json' // we will be sending JSON
}
};
// if params exists and method is GET, add query string to url
// otherwise, just add params as a "body" property to the options object
if (params) {
if (method === 'GET') {
url += '?' + objectToQueryString(params);
} else {
options.body = JSON.stringify(params); // body should match Content-Type in headers option
}
}
const response = await fetch(_apiHost + url, options);
const result = await response.json();
return result;
}
function objectToQueryString(obj) {
return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}
// ...
创建公共方法
太棒了!现在让我们创建四个公共方法,用于发出不同的请求。我们将在模块的最底部导出一个引用这四个方法的对象。
// Fetch.js
// ...
function get(url, params) {
return request(url, params);
}
function create(url, params) {
return request(url, params, 'POST');
}
function update(url, params) {
return request(url, params, 'PUT');
}
function remove(url, params) {
return request(url, params, 'DELETE');
}
错误处理
在我们的模块中添加一些错误处理是一个好主意。一种方法是检查响应的状态码。如果不是 200,那么我们应该返回某种错误消息。
您可以按照自己想要的方式处理错误。在本例中,我们将返回一个包含状态和消息的对象。让我们在获取请求后立即检查状态:
// Fetch.js
// ...
const response = await fetch(_apiHost + url, options);
// show an error if the status code is not 200
if (response.status !== 200) {
return generateErrorResponse('The server responded with an unexpected status.');
}
const result = await response.json();
return result;
}
// A generic error handler that just returns an object with status=error and message
function generateErrorResponse(message) {
return {
status : 'error',
message
};
}
我们做到了!
现在我们可以使用单个模块发出所有四种类型的 API 请求!
异步/等待
我们的 Fetch 调用返回一个 Promise,因此我们需要在调用前使用 await 关键字。此外,await 关键字在顶层代码中不起作用,因此必须将它们包装在异步函数中。这些请求可能如下所示:
import Fetch from './Fetch.js';
// GET
async function getAllBooks() {
const books = await Fetch.get('/books');
}
// POST
async function createBook() {
const request = await Fetch.create('/books', {
title: 'Code and Other Laws of Cyberspace',
author: 'Lawrence Lessig'
});
}
// PUT
async function updateBook(bookId) {
const request = await Fetch.update('/books/' + bookId, {
title: 'How to Live on Mars',
author: 'Elon Musk'
});
}
// DELETE
async function removeBook(bookId) {
const request = await Fetch.remove('/books/' + bookId);
}
我们完成了 API 接口模块! - 后续步骤
我希望您喜欢本教程并且可以利用它来提高工作效率!
您可以向此模块以及 fetch 请求本身添加各种其他选项。例如,您需要在 fetch 方法中添加哪些参数来支持 CORS 请求?
您将如何处理文件上传?
祝您取物愉快!
// Fetch.js
const _apiHost = 'https://api.service.com/v1';
async function request(url, params, method = 'GET') {
const options = {
method,
headers: {
'Content-Type': 'application/json'
}
};
if (params) {
if (method === 'GET') {
url += '?' + objectToQueryString(params);
} else {
options.body = JSON.stringify(params);
}
}
const response = await fetch(_apiHost + url, options);
if (response.status !== 200) {
return generateErrorResponse('The server responded with an unexpected status.');
}
const result = await response.json();
return result;
}
function objectToQueryString(obj) {
return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}
function generateErrorResponse(message) {
return {
status : 'error',
message
};
}
function get(url, params) {
return request(url, params);
}
function create(url, params) {
return request(url, params, 'POST');
}
function update(url, params) {
return request(url, params, 'PUT');
}
function remove(url, params) {
return request(url, params, 'DELETE');
}
export default {
get,
create,
update,
remove
};