Modern fetch-based HTTP client for the browser.
fetch + request abortion + timeout support + request and response interceptions
import Fennch from "fennch";
const api = Fennch({
baseUri: "http://awesome.app/api"
})
async function apiCall() {
const result = await api.get("/awesome-data", {
params: {
awesome: "really",
params: "cool"
}
});
/* ###### Under the hood ######
const result = await fetch("http://awesome.app/api/awesome-data?awesome=really¶ms=cool", {
method: "GET"
})
return {
...response,
body: await response.json() // if Content-Type is 'application/json'
}
#############################
*/
/*
`result` is a FResponse object which is Proxy that wraps native Response object.
`result.headers` and `result.body` is already parsed and can accessed right away.
*/
console.log(result.body) => /* => Awesome response! */
}
const api = Fennch({
baseUri: "http://awesome.app/api"
});
const MySuperComponent = {
currentRequest: null,
handleUserAbort(req) {
this.currentRequest.abort();
},
async apiCall() {
this.currentRequest = api.get("/awesome-data", {
params: {
awesome: "really",
params: "cool"
}
});
let result;
try {
result = await currentRequest;
} catch (err) {
result = err;
}
return result;
}
};
// I want make a request
MySuperComponent.apiCall()
.then(res => res)
.catch(err => {
console.log(err); // => 'Request aborted'
});
// Oh, wait, I changed my mind!
MySuperComponent.handleUserAbort();
// Global timeout
const api = Fennch({
baseUri: "http://awesome.app/api",
timeout: 10000
});
async function apiCall() {
try {
await api.get("/awesome-data", {
params: {
awesome: "really",
params: "cool"
}
});
} catch (err) {
// If request pednding more than 10 sec
console.log(err.toString()) // -> "Timeout exceeded"
}
}
// Timeout per-request, overrides global value
async function apiCallWithTimeout() {
try {
await api.get("/awesome-data", {
timeout: 20000,
params: {
awesome: "really",
params: "cool"
}
});
} catch (err) {
// If request pednding more than 20 sec
console.log(err.toString()) // -> "Timeout exceeded"
}
}
You can register any number of interceptors using register()
method.
It returns function that can be used to unregister this interceptor.
const unregister = fennch.interceptor.register({
request(request) {}, // Must return FRequest object, for example `request` that passed as an argument
requestError(error) {},
response(response) {}, // Must return FResponse object, for example `request` that passed as an argument
responseError(error) {}
})
unregister() // unregister interceptor
Simple example:
const api = Fennch({
baseUri: "http://awesome.app/api"
});
const unregister = api.interceptor.register({
request(request) {
/* Making some tweaks in request */
/*...*/
return request // Interceptor *must* return request
},
requestError(request) {
/* Making some tweaks in request */
/*...*/
return Promise.resolve(request)
},
response(response) {
/* Making some tweaks in response */
/*...*/
return response // Interceptor *must* return response
},
responseError(err) {
/* If request is aborted adding `cancel` property to error */
if (err.toString() === 'AbortError') {
err.cancel = true
return Promise.reject(err)
}
if (!err.response && err.message === 'Network Error') {
err = networkErrorResolver(err)
return Promise.resolve(err)
}
return err
}
})
Example for refreshing authorization token using interceptor:
const accessToken = "some_access_token"
const refreshToken = "some_refresh_token"
const api = Fennch({
baseUri: "http://awesome.app/api",
headers: {
"Content-Type": "application/json",
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`
},
});
api.interceptors.register({
async response(response) {
if (response.status === 401) {
try {
const refreshed = await api.post('/refresh_token', {
headers: {
Authorization: `Bearer ${refreshToken}`
}
})
const newAccessToken = refreshed.body.token
const request = response.request
request.headers.Authorization = `Bearer ${newAccessToken}`
return api.req(request)
} catch (error) {
return Promise.reject(error)
}
}
}
})