使用async & await 和 TypeScript进行数据获取

发布于1/17/2020 来自:「前端知否」微信公众号

fetch API是一个原生JavaScript函数,可用于与Web服务进行交互。我们如何在async和await中使用fetch?以及如何将其与TypeScript一起使用以获得强类型的响应?

使用async & await

我们可以创建一个名为http的函数,该函数包装fetch以允许使用者使用asyncawait

export const http = async (request: RequestInfo): Promise<any> => {
return new Promise(resolve => {
fetch(request)
.then(response => response.json())
.then(body => {
resolve(body);
});
});
};

/* 使用示例 */
const data = await http("https://jsonplaceholder.typicode.com/todos");

我们把fetch的调用包装在Promise中,并对响应正文进行解析。这样,代码使用起来就非常简洁。

设置响应数据类型

请注意,在上面的示例中,我们将响应正文的类型设置为any。下边让我们将其类型化一下:

export const http = <T>(request: RequestInfo): Promise<T> => {
return new Promise(resolve => {
fetch(request)
.then(response => response.json())
.then(body => {
resolve(body);
});
});
};

/* 使用示例 */
interface ITodo {
userId: number;
id: number;
title: string;
completed: boolean;
}

const data = await http<ITodo[]>("https://jsonplaceholder.typicode.com/todos");

现在,http函数为响应主体设置了通用泛型参数。在上边代码中,我们的数据变量被强类型化为ITodo []

整个响应数据

目前,我们只获得返回的响应主体。我们可能需要响应中的其他信息,例如headers。让我们再次完善功能:

interface IHttpResponse<T> extends Response {
parsedBody?: T;
}

export const http = <T>(request: RequestInfo): Promise<IHttpResponse<T>> => {
let response: IHttpResponse<T>;
return new Promise(resolve => {
fetch(request)
.then(res => {
response = res;
return res.json();
})
.then(body => {
response.parsedBody = body;
resolve(response);
});
});
};

/* 使用示例
interface ITodo {
userId: number;
id: number;
title: string;
completed: boolean;
}

const response = await http<ITodo[]>(
"https://jsonplaceholder.typicode.com/todos"
);

我们扩展了标准Response类型以包括已解析的响应主体。然后,我们会返回完整的响应。现在,我们在使用代码中获得了完整的响应。

处理HTTP错误代码

现在,让我们增强http函数来处理HTTP错误代码。如果请求不成功,我们可以在响应对象中使用ok属性来reject promise。如果发生网络错误,我们也可以拒绝响应:

export interface IHttpResponse<T> extends Response {
parsedBody?: T;
}

export const http = <T>(request: RequestInfo): Promise<IHttpResponse<T>> => {
return new Promise((resolve, reject) => {
let response: IHttpResponse<T>;
fetch(request)
.then(res => {
response = res;
return res.json();
})
.then(body => {
if (response.ok) {
response.parsedBody = body;
resolve(response);
} else {
reject(response);
}
})
.catch(err => {
reject(err);
});
});
};

/* 使用示例 */
let response: IHttpResponse<ITodo[]>;

try {

response = await http<ITodo[]>("https://jsonplaceholder.typicode.com/todosX");

console.log("response", response);

} catch (response) {

console.log("Error", response);

}

我们可以在代码中使用try ... catch来捕获错误。

HTTP特定方法

通过调用http函数,我们可以使用除GET以外的HTTP方法:

const response = await http<{ id: number }>(
new Request("https://jsonplaceholder.typicode.com/posts", {
method: "post",
body: JSON.stringify({ title: "my post", body: "some content" })
})
);

这不是世界末日,但我们可以通过为不同的HTTP方法提供特定的功能,使用起来会变得容易一些:

export const get = async <T>(
path: string,
args: RequestInit = { method: "get" }
): Promise<IHttpResponse<T>> => {
return await http<T>(new Request(path, args));
};

export const post = async <T>(
path: string,
body: any,
args: RequestInit = { method: "post", body: JSON.stringify(body) }
): Promise<IHttpResponse<T>> => {
return await http<T>(new Request(path, args));
};

export const put = async <T>(
path: string,
body: any,
args: RequestInit = { method: "put", body: JSON.stringify(body) }
): Promise<IHttpResponse<T>> => {
return await http<T>(new Request(path, args));
};

...

/* 使用示例 */
const response = await post<{ id: number }>(
"https://jsonplaceholder.typicode.com/posts",
{ title: "my post", body: "some content" }
);

因此,这些函数调用基本的http函数,但设置了正确的HTTP方法,并为我们序列化了参数。

现在,代码使用起来更加简单!

最后

使用一些不错的包装函数,我们可以轻松地将异步获取和等待以及TypeScript与fetch一起使用。我们还选择在发生HTTP错误时引发错误,这可以说是HTTP库的一种较常见的行为。具有每种HTTP方法的函数,可以非常轻松地与Web服务进行交互。