Skip to main content
Version: v3

Request Middleware

Usage scope

Client useHook

You can set request middleware for all useHooks to freely control request behavior, providing powerful capabilities that can control almost all behaviors of a request. Whether it is a simple or complex request strategy, you may use it. Next, let's see what it can do.

Middleware function

Request middleware is an asynchronous function. The following is a simple request middleware that prints some information before and after the request, without changing any request behavior.

useRequest(todoList, {
async middleware(_, ​​next) {
console.log('before request');
await next();
console.log('after requeste');
}
});

The next function is an asynchronous function. Calling it will continue to send requests. At this time, the loading state will be set to true, and then the request will be sent. The return value of next is a Promise instance with response data. You can manipulate the return value in the middleware function.

Ignore requests

When you don't want to make a request, you can ignore the request by not calling next, as if the request has never been made. For example, do not make a request when a listening field changes in useWatcher.

useWatcher(() => todoList(), [state1], {
middleware: async (_, next) => {
if (state1 === 'a') {
return next();
}
}
});

Control response data

The return value of the middleware function will be used as the response data of this request for subsequent processing. If the middleware does not return any data but calls next, the response data of this request will be used for subsequent processing.

// Convert response data and return
useRequest(todoList, {
async middleware(_, ​​next) {
const result = await next();
result.code = 500;
return result;
}
});

// The response data of this request will be used for subsequent processing
useRequest(todoList, {
async middleware(_, ​​next) {
await next();
}
});

// The string abc will be used as the response data
useRequest(todoList, {
async middleware(_, ​​next) {
await next();
return 'abc';
}
});

Change request

Sometimes you want to change the request. At this time, you can specify another method instance in next. When sending the request, the information in this method will be requested. At the same time, you can also use next to set whether to force the request to penetrate the cache. This is also very simple.

useRequest(todoList, {
async middleware(_, ​​next) {
await next({
// Change the method instance of the request
method: newMethodInstance,

// Force the request this time
force: true
});
}
});

Control errors

Capture errors

In the middleware, you can capture the request error generated in next. After capturing, the global onError hook will no longer be triggered.

useRequest(todoList, {
async middleware(_, ​​next) {
try {
await next();
} catch (e) {
console.error('Caught error', e);
}
}
});

Throw an error

Of course, you can also throw a custom error in the middleware, and even if the request is normal, it will enter the request error process.

// No request is sent, and global and request-level onError will be triggered. If the request is sent through `method.send`, a reject promise instance will be returned
useRequest(todoList, {
async middleware(_, ​​next) {
throw new Error('error on before request');
await next();
}
});

// After the request is successful, global and request-level onError will be triggered. If the request is sent through `method.send`, a reject promise instance will be returned
useRequest(todoList, {
async middleware(_, ​​next) {
await next();
throw new Error('error on after request');
}
});

Control response delay

In the middleware, we can delay the response or respond in advance. In the case of early response, although the response data cannot be obtained, some other data can be returned as response data to participate in subsequent processing.

// Delay response for 1 second
useRequest(todoList, {
async middleware(_, ​​next) {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
return next();
}
});

// Respond immediately and use string abc as response data
useRequest(todoList, {
async middleware(_, ​​next) {
return 'abc';
}
});

More than that

**So far, we have mentioned the use of the second parameter next of the middleware, so what does the first parameter do? **

The first parameter of the middleware contains some information about this request, as well as control functions for the status and events returned in useHook such as loading, data and onSuccess. Let's continue to look down!

Request information included

async function middleware(context, next) {
// Method instance for this request
context.method;

// Parameter array sent by the send function, default is []
context.args;

// Cache data hit by this request
context.cachedResponse;

// Configuration collection of useHook
context.config;

// The various states returned by useHook, it is a state proxy, including the following properties
// loading, data, error, downloading, uploading, and additional states managed by managedStates
context.proxyStates;

// Operation function in current useHook, send, abort
// context.fetch in useFetcher
context.send;
context.abort;
}

Next, let's take a look at what control capabilities are available.

Modify responsive data

Use context.proxyStates to modify the state data of the current useHook. Since alova's useHook is compatible with multiple UI frameworks, proxyStates is a unified state proxy, which is used in a similar way to vue's ref value.

async function middleware(context, next) {
const { loading, data } = context.proxyStates;

// Get loading value
const loadingValue = loading.v;
// Change loading status to true
loading.v = true;

// Modify data status value
data.v = {
/* ... */
};
}

For detailed usage of state proxy, please refer to State Proxy.

Interrupt or repeat request

The abort and send functions (fetch in useFetcher) received by the middleware can also send multiple requests when a request intent is triggered.

A typical use case is request retry. If a request fails after sending a request, it will automatically request again according to a certain strategy. After the retry succeeds, it will trigger onSuccess. The following is a simple request retry example code.

async function middleware(context, next) {
return next().catch(error => {
if (needRetry) {
setTimeout(() => {
context.send(...context.args);
}, retryDelay);
}
return Promise.reject(error);
});
}

If you need to interrupt the request in the middleware, you can call context.abort().

Controlled loading state

In the above content, we know that we can modify responsive data through loading.v, but there will be obstacles when you modify the loading state value loading, because under normal circumstances, the loading state value will be automatically set to true when calling next, and automatically set to false in the response process, which will overwrite the loading state value modified by loading.v. At this time, we can turn on the controlled loading state. After turning it on, the loading state value will no longer be modified in the next function and the response process, and we will be fully controlled.

Let's take request retry as an example. We hope that the loading state will remain true from the beginning of triggering a request intention, through request retry until the request ends.

Use context.controlLoading to turn on the custom control loading state.

async function middleware(context, next) {
context.controlLoading();
const { loading } = context.proxyStates;

// Set to true when the request starts
loading.v = true;
return next()
.then(value => {
// Set to false after the request is successful
loading.v = false;
return value;
})
.catch(error => {
if (needRetry) {
setTimeout(() => {
context.send(...context.args);
}, retryDelay);
} else {
// Set to false when no longer retrying
loading.v = false;
}
return Promise.reject(error);
});
}