245 lines
7.1 KiB
Markdown
245 lines
7.1 KiB
Markdown
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
|
|
|
# Ajax
|
|
|
|
`ajax` is a small wrapper around `fetch` which:
|
|
|
|
- Allows globally registering request and response interceptors
|
|
- Throws on 4xx and 5xx status codes
|
|
- Prevents network request if a request interceptor returns a response
|
|
- Supports a JSON request which automatically encodes/decodes body request and response payload as JSON
|
|
- Adds accept-language header to requests based on application language
|
|
- Adds XSRF header to request if the cookie is present
|
|
|
|
## How to use
|
|
|
|
### Installation
|
|
|
|
```sh
|
|
npm i --save @lion/ajax
|
|
```
|
|
|
|
### Relation to fetch
|
|
|
|
`ajax` delegates all requests to fetch. `ajax.request` and `ajax.requestJson` have the same function signature as `window.fetch`, you can use any online resource to learn more about fetch. [MDN](http://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) is a great start.
|
|
|
|
### Example requests
|
|
|
|
#### GET request
|
|
|
|
```js
|
|
import { ajax } from '@lion/ajax';
|
|
|
|
const response = await ajax.request('/api/users');
|
|
const users = await response.json();
|
|
```
|
|
|
|
#### POST request
|
|
|
|
```js
|
|
import { ajax } from '@lion/ajax';
|
|
|
|
const response = await ajax.request('/api/users', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username: 'steve' }),
|
|
});
|
|
const newUser = await response.json();
|
|
```
|
|
|
|
### JSON requests
|
|
|
|
We usually deal with JSON requests and responses. With `requestJson` you don't need to specifically stringify the request body or parse the response body:
|
|
|
|
#### GET JSON request
|
|
|
|
```js
|
|
import { ajax } from '@lion/ajax';
|
|
|
|
const { response, body } = await ajax.requestJson('/api/users');
|
|
```
|
|
|
|
#### POST JSON request
|
|
|
|
```js
|
|
import { ajax } from '@lion/ajax';
|
|
|
|
const { response, body } = await ajax.requestJson('/api/users', {
|
|
method: 'POST',
|
|
body: { username: 'steve' },
|
|
});
|
|
```
|
|
|
|
### Error handling
|
|
|
|
Different from fetch, `ajax` throws when the server returns a 4xx or 5xx, returning the request and response:
|
|
|
|
```js
|
|
import { ajax } from '@lion/ajax';
|
|
|
|
try {
|
|
const users = await ajax.requestJson('/api/users');
|
|
} catch (error) {
|
|
if (error.response) {
|
|
if (error.response.status === 400) {
|
|
// handle a specific status code, for example 400 bad request
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
} else {
|
|
// an error happened before receiving a response, ex. an incorrect request or network error
|
|
console.error(error);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Ajax Cache
|
|
|
|
A caching library that uses `lion-web/ajax` and adds cache interceptors to provide caching for use in
|
|
frontend `services`.
|
|
|
|
> Technical documentation and decisions can be found in
|
|
> [./docs/technical-docs.md](./docs/technical-docs.md)
|
|
|
|
### Getting started
|
|
|
|
Consume the global `ajax` instance and add the interceptors to it, using a cache configuration
|
|
which is applied on application level. If a developer wants to add specifics to cache behavior
|
|
they have to provide a cache config per action (`get`, `post`, etc.) via `cacheOptions` field of local ajax config,
|
|
see examples below.
|
|
|
|
> **Note**: make sure to add the **interceptors** only **once**. This is usually
|
|
> done on app-level
|
|
|
|
```js
|
|
import {
|
|
ajax,
|
|
cacheRequestInterceptorFactory,
|
|
cacheResponseInterceptorFactory,
|
|
} from '@lion-web/ajax.js';
|
|
|
|
const globalCacheOptions = {
|
|
useCache: true,
|
|
timeToLive: 1000 * 60 * 5, // 5 minutes
|
|
};
|
|
// Cache is removed each time an identifier changes,
|
|
// for instance when a current user is logged out
|
|
const getCacheIdentifier = () => getActiveProfile().profileId;
|
|
|
|
ajax.addRequestInterceptor(cacheRequestInterceptorFactory(getCacheIdentifier, globalCacheOptions));
|
|
ajax.addResponseInterceptor(
|
|
cacheResponseInterceptorFactory(getCacheIdentifier, globalCacheOptions),
|
|
);
|
|
|
|
const { response, body } = await ajax.requestJson('/my-url');
|
|
```
|
|
|
|
### Ajax cache example
|
|
|
|
```js
|
|
import {
|
|
ajax,
|
|
cacheRequestInterceptorFactory,
|
|
cacheResponseInterceptorFactory,
|
|
} from '@lion-web/ajax';
|
|
|
|
const getCacheIdentifier = () => getActiveProfile().profileId;
|
|
|
|
const globalCacheOptions = {
|
|
useCache: false,
|
|
timeToLive: 50, // default: one hour (the cache instance will be replaced in 1 hour, regardless of this setting)
|
|
methods: ['get'], // default: ['get'] NOTE for now only 'get' is supported
|
|
// requestIdentificationFn: (requestConfig) => { }, // see docs below for more info
|
|
// invalidateUrls: [], see docs below for more info
|
|
// invalidateUrlsRegex: RegExp, // see docs below for more info
|
|
};
|
|
|
|
// pass a function to the interceptorFactory that retrieves a cache identifier
|
|
// ajax.interceptors.request.use(cacheRequestInterceptorFactory(getCacheIdentifier, cacheOptions));
|
|
// ajax.interceptors.response.use(
|
|
// cacheResponseInterceptorFactory(getCacheIdentifier, cacheOptions),
|
|
// );
|
|
|
|
class TodoService {
|
|
constructor() {
|
|
this.localAjaxConfig = {
|
|
cacheOptions: {
|
|
invalidateUrls: ['/api/todosbykeyword'], // default: []
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns all todos from cache if not older than 5 minutes
|
|
*/
|
|
getTodos() {
|
|
return ajax.requestJson(`/api/todos`, this.localAjaxConfig);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
getTodosByKeyword(keyword) {
|
|
return ajax.requestJson(`/api/todosbykeyword/${keyword}`, this.localAjaxConfig);
|
|
}
|
|
|
|
/**
|
|
* Creates new todo and invalidates cache.
|
|
* `getTodos` will NOT take the response from cache
|
|
*/
|
|
saveTodo(todo) {
|
|
return ajax.requestJson(`/api/todos`, { method: 'POST', body: todo, ...this.localAjaxConfig });
|
|
}
|
|
}
|
|
```
|
|
|
|
If a value returned by `cacheIdentifier` changes the cache is reset. We avoid situation of accessing old cache and proactively clean it, for instance when a user session is ended.
|
|
|
|
### Ajax cache Options
|
|
|
|
```js
|
|
const cacheOptions = {
|
|
// `useCache`: determines wether or not to use the cache
|
|
// can be boolean
|
|
// default: false
|
|
useCache: true,
|
|
|
|
// `timeToLive`: is the time the cache should be kept in ms
|
|
// default: 0
|
|
// Note: regardless of this setting, the cache instance holding all the caches
|
|
// will be invalidated after one hour
|
|
timeToLive: 1000 * 60 * 5,
|
|
|
|
// `methods`: an array of methods on which this configuration is applied
|
|
// Note: when `useCache` is `false` this will not be used
|
|
// NOTE: ONLY GET IS SUPPORTED
|
|
// default: ['get']
|
|
methods: ['get'],
|
|
|
|
// `invalidateUrls`: an array of strings that for each string that partially
|
|
// occurs as key in the cache, will be removed
|
|
// default: []
|
|
// Note: can be invalidated only by non-get request to the same url
|
|
invalidateUrls: ['/api/todosbykeyword'],
|
|
|
|
// `invalidateUrlsRegex`: a RegExp object to match and delete
|
|
// each matched key in the cache
|
|
// Note: can be invalidated only by non-get request to the same url
|
|
invalidateUrlsRegex: /posts/
|
|
|
|
// `requestIdentificationFn`: a function to provide a string that should be
|
|
// taken as a key in the cache.
|
|
// This can be used to cache post-requests.
|
|
// default: (requestConfig, searchParamsSerializer) => url + params
|
|
requestIdentificationFn: (request, serializer) => {
|
|
return `${request.url}?${serializer(request.params)}`;
|
|
},
|
|
};
|
|
```
|
|
|
|
## Considerations
|
|
|
|
## Fetch Polyfill
|
|
|
|
For IE11 you will need a polyfill for fetch. You should add this on your top level layer, e.g. your application.
|
|
|
|
[This is the polyfill we recommend](https://github.com/github/fetch). It also has a [section for polyfilling AbortController](https://github.com/github/fetch#aborting-requests)
|