7.1 KiB
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
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 is a great start.
Example requests
GET request
import { ajax } from '@lion/ajax';
const response = await ajax.request('/api/users');
const users = await response.json();
POST request
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
import { ajax } from '@lion/ajax';
const { response, body } = await ajax.requestJson('/api/users');
POST JSON request
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:
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
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
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
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
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. It also has a section for polyfilling AbortController