A few examples follow, going from basic through to adding transformations after the request and/or error handling:
Basic:
// Implementation code where T is the returned data shape
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<T>()
})
}
// Consumer
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Data transformations:
Often you may need to do some tweaks to the data before its passed to the consumer, for example, unwrapping a top level data attribute. This is straight forward:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => { /* <-- data inferred as { data: T }*/
return data.data
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Error handling:
I'd argue that you shouldn't be directly error catching directly within this service, instead, just allowing it to bubble, but if you need to, you can do the following:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => {
return data.data
})
.catch((error: Error) => {
externalErrorLogging.error(error) /* <-- made up logging service */
throw error /* <-- rethrow the error so consumer can still catch it */
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Edit
There has been some changes since writing this answer a while ago. As mentioned in the comments, response.json<T>
is no longer valid. Not sure, couldn't find where it was removed.
For later releases, you can do:
// Standard variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<T>
})
}
// For the "unwrapping" variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<{ data: T }>
})
.then(data => {
return data.data
})
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…