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
url
option - Enhanced
Request#clone(...options)
method - New
responseType
option - New
query
option - Enhanced
body
option - New
type
option - New
simple
option - Polyfill
AbortController
- New
queryStringify
option - New
queryParse
option - New
queryTransformer
option - New
urlTransformer
option - New
headersTransformer
option - New
bodyTransformer
option - New
responseTransformer
option - New
responseDataTransformer
option - New
errorTransformer
option
- New
- Contributing
- License
Using npm:
$ npm install fetch-extra
Or 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
options
is a string, it is treated as aURL
- If
options
is a object, it is treated asRequest
options. 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