Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add subpackage @sa/alova #640

Merged
merged 4 commits into from
Oct 9, 2024
Merged

Conversation

JOU-amjs
Copy link
Contributor

@JOU-amjs JOU-amjs commented Oct 9, 2024

此pr包含只添加了@sa/alova子包,通过77行代码实现了比@sa/axios更丰富的特性,具体如下:

  1. 尽量与@sa/axios的封装设计保持了一致
  2. 自带了token无感刷新特性,不再需要自行实现,并且在刷新token期间发送的请求将会挂起等待,刷新完成自动重新请求
  3. 因不再需要自行实现token刷新,去除了onBackendFail钩子
  4. 可自定义请求适配器,默认使用fetch适配器

@Azir-11
Copy link
Member

Azir-11 commented Oct 9, 2024

onBackendFail并非只有token刷新一个作用,该钩子方法是为了给用户提供统一的源于后端失败的处理用的

@JOU-amjs
Copy link
Contributor Author

JOU-amjs commented Oct 9, 2024

@Azir-11 因为我看@sa/axios中提供了onBackendFailonError,按我的理解onError才是统一的处理后端失败的钩子,源码如下:

instance.interceptors.response.use(
async response => {
const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
if (responseType !== 'json' || opts.isBackendSuccess(response)) {
return Promise.resolve(response);
}
const fail = await opts.onBackendFail(response, instance);
if (fail) {
return fail;
}
const backendError = new AxiosError<ResponseData>(
'the backend request error',
BACKEND_ERROR_CODE,
response.config,
response.request,
response
);
await opts.onError(backendError);
return Promise.reject(backendError);
},
async (error: AxiosError<ResponseData>) => {
await opts.onError(error);
return Promise.reject(error);
}
);

所以我才在@sa/alova中保留了onError钩子,作用相同。

@Azir-11
Copy link
Member

Azir-11 commented Oct 9, 2024

onBackendFail是后端请求在业务上表示失败时调用的异步函数
onError是当请求失败时调用的函数(包括请求失败和后端业务上的失败请求)

onBackendFail是更突出业务失败用的

@Azir-11
Copy link
Member

Azir-11 commented Oct 9, 2024

当然只留onError其实也可以做到onBackendFail里的事,但是我觉得为了保证过渡平滑,还是保留比较好

@JOU-amjs
Copy link
Contributor Author

JOU-amjs commented Oct 9, 2024

@Azir-11 在修改时我是发现了这段代码,以下两段代码中都对VITE_SERVICE_MODAL_LOGOUT_CODESVITE_SERVICE_EXPIRED_TOKEN_CODES做了判断

const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
if (logoutCodes.includes(responseCode)) {
handleLogout();
return null;
}
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
// prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout);
window.$dialog?.error({
title: $t('common.error'),
content: response.data.msg,
positiveText: $t('common.confirm'),
maskClosable: false,
closeOnEsc: false,
onPositiveClick() {
logoutAndCleanup();
},
onClose() {
logoutAndCleanup();
}
});
return null;
}
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
if (expiredTokenCodes.includes(responseCode)) {
const success = await handleExpiredRequest(request.state);
if (success) {
const Authorization = getAuthorization();
Object.assign(response.config.headers, { Authorization });
return instance.request(response.config) as Promise<AxiosResponse>;
}
}
return null;
},

onError(error) {
// when the request is fail, you can show error message
let message = error.message;
let backendErrorCode = '';
// get backend error message and code
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.msg || message;
backendErrorCode = String(error.response?.data?.code || '');
}
// the error message is displayed in the modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(backendErrorCode)) {
return;
}
// when the token is expired, refresh token and retry request, so no need to show error message
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
if (expiredTokenCodes.includes(backendErrorCode)) {
return;
}
showErrorMsg(request.state, message);

这里我觉得是多余的,而且虽说onError是统一的错误处理,但从第二段代码可见,它依然还在处理后端业务上的失败

// get backend error message and code
if (error.code === BACKEND_ERROR_CODE) {
  message = error.response?.data?.msg || message;
  backendErrorCode = String(error.response?.data?.code || '');
}

所以我才统一使用了onError来处理,不需要分别处理各种responseCode的判断,在达到相同效果的前提下代码也简洁了一些。

如果需要分别处理,也可以通过response值分辨是业务上的错误还是网络错误。

{
  // ...
  onError(error, response) {
    if (response) {
      // 处理后端业务错误
    } else {
      // 处理网络错误
    }
  }
}

最后,以上是我只保留onError的出发点,当然决定权在于你们,希望可以斟酌下利弊

@Azir-11
Copy link
Member

Azir-11 commented Oct 9, 2024

确实,你说的也没什么问题,但是还是需要考虑一下现在正在使用的用户的,去掉onBackendFail或许可行,但是起码需要在至少v1.4才能做

@JOU-amjs
Copy link
Contributor Author

JOU-amjs commented Oct 9, 2024

@Azir-11 我是增量添加一个@sa/alova子包,并不是在@sa/axios上修改的😂,这个结果是和你们创始人沟通决定的

@Azir-11
Copy link
Member

Azir-11 commented Oct 9, 2024

同属于请求封装,在大部分钩子相同的情况下,某一些不同,会带来心智负担

@honghuangdc honghuangdc merged commit 2b94264 into soybeanjs:main Oct 9, 2024
honghuangdc pushed a commit that referenced this pull request Oct 9, 2024
* feat(packages): add @sa/alova

* typo(packages): add types & update code

* feat: add subpackage @sa/alova

---------

Co-authored-by: allenli178 <allenli178@qq.com>
Co-authored-by: 子殊 <yuyan@zishudeMac-mini.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants