假设我们有一个组件通过 prop 接收资源的 ID,resourceId
.组件应该从 API 获取相应的资源并显示它,同时还处理加载/错误状态(遵循类似于 this one 的模式)。
从外部 API 获取资源的函数也返回 abort
如果调用该函数,则会导致请求立即拒绝。当resourceId
更改,任何进行中的请求都应中止以支持新请求。对于它的值(value),我正在使用 fetch()
和 AbortController
为了这。
使用 Vue 3 和组合 API,我想出了一个看起来像这样的实现:
const loading = ref(false);
const data = ref(null);
const error = ref(null);
watchEffect(async (onCancel) => {
loading.value = true;
data.value = error.value = null;
const { response, abort } = fetchResourceFromApi(props.resourceId);
onCancel(abort);
try {
data.value = await (await response).json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
});
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ data }}</div>
这在大多数情况下都可以正常工作,但只要取消播放就会中断。如果 resourceId
在最后一个 API 请求完成之前发生更改,会发生以下事件顺序:abort()
被调用 watchEffect
回调被调用,设置 loading
, error
, 和 data
catch
和 finally
调用来自原始请求的 block ,设置 loading
和 error
loading
和 data
这会导致意外状态,其中
loading
设置为 false
当第二个请求进行中时,error
包含由中止第一个请求引发的异常,以及 data
包含来自第二个请求的值。是否有任何设计模式或解决方法可以解决此问题?
最佳答案
如果事件的顺序如您所描述:
watchEffect
abort
watchEffect
watchEffect
然后,您可以保留一个计数器并使用它来跟踪当前正在运行的请求是什么(实际上它是请求 id,或某些库调用它的版本)。每次
watchEffect
被调用,增加计数器并拍摄当前值的快照。如果当前计数器与您开始使用的计数器值相同,请检查您的捕获并最终阻止。如果它们不匹配,则意味着您正在处理陈旧的请求错误,因此您可以跳过错误和加载状态的修改。const {ref, watchEffect} = Vue;
const App = {
setup() {
const resourceId = ref(0);
const loading = ref(false);
const data = ref(null);
const error = ref(null);
// fake an api fetch method to asynchronously return
// a result after 3 seconds.
const fetchFor = (id) => {
let abort;
const response = new Promise((resolve, reject) => {
abort = () => reject(`request ${id} aborted`);
setTimeout(() => resolve({msg: "result for " + id}), 3000);
});
return {response, abort};
};
let version = 0;
watchEffect(async (onCancel) => {
version ++;
const currentVersion = version;
loading.value = true;
data.value = error.value = null;
const { response, abort } = fetchFor(resourceId.value);
onCancel(abort);
try {
data.value = await response;
} catch (e) {
if (currentVersion === version) {
error.value = e;
}
} finally {
if (currentVersion === version) {
loading.value = false;
}
}
});
return {resourceId, loading, data, error};
}
};
const app = Vue.createApp(App);
app.mount("#app");
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="app">
<button @click="resourceId++">Request</button>
<div> last request is {{resourceId}} </div>
<div> loading = {{loading}} </div>
<div> data = {{data?.msg}} </div>
<div> error = {{error}} </div>
</div>
关于javascript - 如何使用 watchEffect() 防止中止的异步请求破坏状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72637898/