假设我们有一个组件,可以通过道具, resourceid
接收资源的ID。该组件应从API中获取相应的资源并显示它,同时还处理加载/错误状态(遵循类似于这个)。
从外部API获取资源的功能还返回中止
函数,如果调用,该函数会导致请求立即拒绝。当 ResourceD
更改时,任何飞行中的请求都应中止,而有利于新请求。对于它的价值,我正在使用 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>
在大多数情况下,这种情况正常,但是每当取消播放时都会断裂。如果 ResourceD
在上次API请求完成之前进行更改,则发生以下事件的顺序:
-
abort()
被称为
WatchEffect
呼叫,设置加载
,错误
和 data
-
catch
和最后
原始请求的块被调用,设置加载
和错误
- 第二API请求完成并设置
加载
和 data
此导致出乎意料的状态,其中<代码>加载设置为 false
时,第二个请求在飞行中,错误
包含通过中止第一个请求而提出的异常, data data < /代码>包含第二个请求中的值。
是否有任何可以解决此问题的设计模式或解决方法?
Say we have a component that receives the ID of a resource through a prop, resourceId
. The component should fetch the corresponding resource from an API and display it, while also handling loading/error states (following a pattern similar to this one).
The function that fetches the resource from the external API also returns an abort
function which, if called, causes the request to immediately reject. When resourceId
changes, any in-flight requests should be aborted in favor of a new request. For what it's worth, I'm using fetch()
and AbortController
for this.
Using Vue 3 with the composition API, I came up with an implementation that looks something like this:
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>
This works fine under most circumstances, but breaks whenever the cancellation comes in to play. If resourceId
changes before the last API request has finished, the following order of events happens:
abort()
gets called
watchEffect
callback gets called, setting loading
, error
, and data
catch
and finally
blocks from original request are called, setting loading
and error
- Second API request completes and sets
loading
and data
This results in an unexpected state where loading
is set to false
while the second request is in flight, error
contains the exception raised by aborting the first request, and data
contains the value from the second request.
Are there any design patterns or workarounds that can deal with this problem?
发布评论
评论(1)
如果事件的顺序如您所述:
WATCHEFFECT
中止
WatchEffect
然后,您可以保留一个计数器并使用它来跟踪飞行请求中的当前内容(实际上是一个请求ID或某些库称为版本)。每次调用
WATCHEFFECT
都被调用,增加计数器并拍摄当前值的快照。检查您的捕获量,最后阻止目前的计数器与您启动的计数器值相同。如果它们不匹配,则意味着您正在处理过时的请求错误,因此您可以跳过错误和加载状态的修改。if the sequence of events is as you described:
watchEffect
abort
watchEffect
watchEffect
Then you can keep a counter and use it to track what the current in flight request is (effectively it is a request id, or version as some libraries call it). Every time
watchEffect
is called, increase the counter and take a snapshot of the current value. Check in your catch and finally block if the present counter is the same as the counter value you started with. In case they mismatch, it means you are handling stale request errors so you can skip the modification of error and loading status.