fetch-extra v1.0.1
fetch-extra
Extra features for whatwg fetch and Request like query object, JSON body, timeout, abort, transformers. Works for browser and Node.js.
Table of Contents
- 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
Installations
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
fetch
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 */
})();Syntax
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.
Description
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.
Request
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");
})();Syntax
\<Request> new Request(...options)
...options \<...String|Object|Request>
All options are the same with fetch(...options).
Description
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.
Why
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.
New Request#fetch(...options) method
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();Enhanced url option
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/" */Enhanced Request#clone(...options) method
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)
New responseType option
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.
New query option
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.
Enhanced body option
const results = await swRequest.fetch({
method: "POST",
body: { name: "Luke Skywalker" } /* <-- could be a JSON */
});
/* final body will be '{"name":"Luke Skywalker"}' */New type option
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.
New simple option
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 */
});Polyfill AbortController
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);
});
})();New queryStringify option
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.
New queryParse option
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.
New queryTransformer option
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;
},
});
/* ... */New urlTransformer option
Like queryTransformer, but transform url.
New headersTransformer option
Like queryTransformer, but transform headers.
New bodyTransformer option
Like queryTransformer, but transform body.
New responseTransformer option
Transform response instance.
const baseRequest = new Request({
responseType: "json",
responseTransformer(response) {
/* <-- responseTransformer */
if (response.status === 404) {
throw new Error("Page not found");
}
return response;
}
});
/* ... */New responseDataTransformer option
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;
}
});
/* ... */New errorTransformer option
Transform error or rejection.
const baseRequest = new Request({
errorTransformer(error) {
/* <-- errorTransformer */
if (error.name === "Abort") {
console.warn("Fetch aborted");
}
return error;
}
});
/* ... */Contributing
Please checkout CONTRIBUTING.md
License
MIT