Extra features for whatwg fetch and Request like query object, JSON body, timeout, abort, transformers. Works for browser and Node.js.
- Table of Contents
- Installations
- fetch
- Request
- New
Request#fetch(...options)method - Enhanced
urloption - Enhanced
Request#clone(...options)method - New
responseTypeoption - New
queryoption - Enhanced
bodyoption - New
typeoption - New
simpleoption - Polyfill
AbortController - New
queryStringifyoption - New
queryParseoption - New
queryTransformeroption - New
urlTransformeroption - New
headersTransformeroption - New
bodyTransformeroption - New
responseTransformeroption - New
responseDataTransformeroption - New
errorTransformeroption
- New
- Contributing
- License
Using npm:
$ npm install fetch-extraOr using 1998 Script Tag:
<script src="https://unpkg.com/fetch-extra@latest/dist/fetch-extra-umd.js"></script>
(Module exposed as `fetchExtra`)To install fetch-extra with window.fetch polyfill, please istall fetch-extra-polyfill instead
import fetch from "fetch-extra";
(async function main() {
const url = "https://swapi.co/api/people/";
const res = await fetch(url, {
method: "POST",
type: "json" /* json Content-Type header */,
responseType: "json" /* short for `await res.json()` */,
query: { token: "asdf" } /* query object */,
body: {
/* json body object */
firstName: "Luke",
familyName: "Skywalker"
}
});
console.log(res.name); /* Luke Skywalker */
})();Promise<Response> fetch(...options)
...options <...String|Object|Request>
- If
optionsis a string, it is treated as aURL - If
optionsis a object, it is treated asRequestoptions. Checkout below for detail. All fetch options are supported
Later options will similarly overwrite earlier ones.
The Fetch API provides an interface for fetching resources.
fetch syntax adapts to WHATWG fetch:
import fetch from "fetch-extra";
(async function main() {
const url = "https://swapi.co/api/people/1/";
const res = await fetch(url, { method: "GET" });
const luke = await res.json();
console.log(luke.name); /* Luke Skywalker */
})();But there are some extra options.
const res = await fetch({
url: "https://swapi.co/api/people/1/",
query: { page: 32 },
responseType: "json",
timeout: 30000
});For more extra options and usages, please checkout below.
import { Request } from "fetch-extra";
(async function main() {
const base = new Request({
url: "https://swapi.co/api/",
type: "json",
responseType: "json",
timeout: 30000,
async headersTransformer(headers) {
headers['access-token'] = await fakeGetToken(),
return headers;
},
});
const people = base.clone("/people");
const starships = base.clone("/starships");
const luke = await people.fetch("/1");
const c3po = await people.fetch("/2");
const starDestroyer = await starships.fetch("/3");
})();<Request> new Request(...options)
...options <...String|Object|Request>
All options are the same with fetch(...options).
The Request interface of the Fetch API represents a resource request.
Request syntax also adapts to WHATWG Request.
import fetch, { Request } from "fetch-extra";
(async function main() {
const url = "https://swapi.co/api/people/1/";
const request = new Request(url);
const res = await fetch(request);
const luke = await res.json();
})();But there are some extra options and methods.
fetch() is useful, but new Request() provides a way to inherit requests. It is recommended to create a base Request instance to share base url, Content-Type header, access token header, response type, error handler (by using errorTransformer()), etc, and then fetch() or clone() the base request.
A shortcut for fetch(request, ...options).
All options are the same with fetch(...options).
import { Request } from "fetch-extra";
const request = new Request(url);
const res = await request.fetch();
const luke = await res.json();Fetching with options:
import { Request } from "fetch-extra";
const request = new Request(url);
const res = await request.fetch({ method: "DELETE" });
const luke = await res.json();The example above is equal to:
import fetch, { Request } from "fetch-extra";
const request = new Request(url);
const res = await fetch(request, { method: "DELETE" });
const luke = await res.json();URLs could be composed.
const baseUrl = "https://swapi.co/api/";
const swRequest = new Request(baseUrl);
const lukeRes = await swRequest.fetch("/people/1/");
/* final URL will be "https://swapi.co/api/people/1/" */
const starShipRes = await swRequest.fetch("/starships/9/");
/* final URL will be "https://swapi.co/api/starships/9/" */To override earlier URL, just give a new URL starts with a protocol (like http:// or https://):
const swRequest = new Request("https://swapi.co/", options);
const pokeRes = swRequest.fetch("https://pokeapi.co/api/v2/");
/* final URL will be "https://pokeapi.co/api/v2/" */const baseRequest = new Request({
headers: { "Content-Type": "application/json" }
});
const swRequest = baseRequest.clone("https://swapi.co/api/");
const luke = await swRequest.fetch("/people/1/");
const c3po = await swRequest.fetch("/people/2/");
const pokeRequest = baseRequest.clone("https://pokeapi.co/api/v2/");
const bulbasaur = await pokeRequest.fetch("/pokemon/1/");The ...options usages are the same with fetch(...options) and new Request(...options)
Returning resolved data with specified type instead of response object.
const options = { responseType: "json" };
const luke = await swRequest.fetch(
options
); /* <-- no need `await res.json()` */
console.log(luke.name); /* Luke Skywalker */In browser, responseType value could be one of arrayBuffer, blob, formData, json or text.
In Node.js, formData is NOT supported.
If responseType is none, it will return the original response object.
const results = await swRequest.fetch({ query: { search: "luke" } });
/* final URL will be "https://swapi.co/api/people?search=luke" */query could be JSON object or string (like name=luke&height=172).
If url has search fields (like https://swapi.co/api/people?search=luke), query string will append to the search fields.
const results = await swRequest.fetch({
method: "POST",
body: { name: "Luke Skywalker" } /* <-- could be a JSON */
});
/* final body will be '{"name":"Luke Skywalker"}' */const results = await swRequest.fetch({
method: 'POST',
type: 'form'
body: { name: 'Luke Skywalker' },
});
/* final body will be 'name=Luke%20Skywalker' */
/* final header['Content-Type'] will be 'application/x-www-form-urlencoded' */type value will auto set to headers Content-Type.
Value form is short for application/x-www-form-urlencoded, and json is short for application/json.
Will throw error if response status is non-2xx.
await swRequest
.fetch({
simple: true,
url: "/400" /* simulate response with 400 HTTP status */
})
.catch(err => {
console.error(err); /* <-- Error: Bad Request */
});Built-in AbortController and AbortSignal polyfill for aborting fetch.
import fetch, { AbortController } from "fetch-extra";
(async function main() {
const abortController = new AbortController();
const fetchPromise = fetch("https://swapi.co/api/people/1", {
signal: abortController.signal
});
abortController.abort();
await fetchPromise.catch(err => {
if (err.name === "AbortError") console.warn("Aborted.");
else console.error(err.message);
});
})();Setting a custom function in charge of serializing query object.
import qs from "qs";
const request = new Request({
queryStringify: qs.stringify
});By default, this function is tiny-querystring stringify function.
Setting a custom function in charge of parsing query string.
import qs from "qs";
const request = new Request({
queryParse: qs.parse
});By default, this function is tiny-querystring parse function.
Setting a function to transform query object, should return a new query object. Will be called before fetching.
const baseRequest = new Request({
queryTransformer: (query) => { /* <-- queryTransformer */
query.accessToken = '<ACCESS_TOKEN>',
return query;
},
});
const swRequest = baseRequest.clone('https://swapi.co/api/');
const results = await swRequest.fetch('/people', {
query: { search: 'luke' },
});
/* final URL will be "https://swapi.co/api/people?search=luke&accessToken=<ACCESS_TOKEN>" */All transformers could return promises.
const baseRequest = new Request({
async queryTransformer(query) { /* <-- async queryTransformer */
query.accessToken = await getTokenAsync(),
return query;
},
});
/* ... */Like queryTransformer, but transform url.
Like queryTransformer, but transform headers.
Like queryTransformer, but transform body.
Transform response instance.
const baseRequest = new Request({
responseType: "json",
responseTransformer(response) {
/* <-- responseTransformer */
if (response.status === 404) {
throw new Error("Page not found");
}
return response;
}
});
/* ... */Like responseTransformer, but transform the data after responseType resolved.
const baseRequest = new Request({
responseType: "json",
responseDataTransformer(json) {
/* <-- responseDataTransformer */
if (json) {
json.fullName = `${json.firstName} ${json.familyName}`;
}
return json;
}
});
/* ... */Transform error or rejection.
const baseRequest = new Request({
errorTransformer(error) {
/* <-- errorTransformer */
if (error.name === "Abort") {
console.warn("Fetch aborted");
}
return error;
}
});
/* ... */Please checkout CONTRIBUTING.md
MIT