阿波罗突变去抖和种族条件

时间:2017-08-14 22:01:51

标签: reactjs graphql apollo react-apollo

(这是https://github.com/apollographql/apollo-client/issues/1886

的后续行动

我正在尝试构建一个文本输入,该输入将在用户输入时更新值。

首次尝试

我首先尝试使用optimisticResponse在用户输入时更新本地缓存。这是有效的,除了它会在每次击键时触发突变。除了通过请求充斥网络之外,还存在网络不一致的问题。最后一个突变可能首先到达,第一个突变到达最后。这导致服务器以过时值结束。以下是这种竞争条件的一个例子:

type: a
mutate request: a
type: b
mutate request: ab
arrives on server: ab
arrives on server: a

现在服务器已经录制了" a"在graphql中,这是不正确的。

添加去抖动

为了缓解这种情况,我对按键事件进行了辩论。虽然这确实有助于上述竞争条件,但它并没有解决它。如果网络比你的去抖阈值慢,那么它仍有可能出现竞争条件。

因为我们现在正在对文本输入进行去抖动,所以我们需要向该React组件引入一个本地状态,以便在用户输入时立即更新(如在github问题中建议的@jbaxleyiii)。现在我们的状态位于两个位置(组件状态和apollo缓存)。

这个问题的一大问题是组件在收到新道具时不会更新。例如。当graphql更新并推送到客户端时。

添加网络队列

因为debounce实际上没有解决竞争条件,所以我添加了一个网络队列(除了去抖动),它将管理变异请求,以确保只有一个一次飞行中的突变。如果在飞行中有一个突变请求时它会收到突变请求,它会将它排队等待第一个返回时被触发。如果已经有一个排队的突变,它将丢弃它并将其替换为新的突变(一次只能有一个项目在队列中)。这是一个例子:

type: a
send mutate request: a
type: b
queues mutate request: ab     <<  wait to send this until "a" comes back
type: c
replaces queued request: abc  << discard the queued request for "ab", it's old now
response from server: a
send mutate request: abc      << send the queued mutation and clear the queue
response from server: abc

这保证我们不会遇到竞争条件(至少从这个客户那里......)

但这种方法存在问题。 optimisticResponse只会在突变发生时更新。如果正在进行突变,我们需要在应用更新optimisicRespose之前等待网络返回。在慢速网络上这段时间可能很长。因此,在上面的示例中,我们无法使用optimisticResponse更新为&#34; abc&#34;直到&#34;发送mutate请求:abc&#34;。

这不是一个大问题,只是一个延迟,但它似乎是我们应该做的事情。

尝试在用户键入

时更新缓存

在文档中,我了解到我可以使用withApollo来访问客户端,并在用户通过writeQuery键入时更新缓存。这取代了对optimisticResponse的需求。但是,当旧的响应返回并从我们下面更新缓存时,就会出现问题。这是一个例子:

action                   | cache
-------------------------+------------
type: a                  | a
mutate request: a        | a
type: b                  | ab
queues request: ab       | ab
response from server: a  | a  << oh no!
mutate request: ab       | a  << we're not using optimisticResponse anymore
... network time ...     | a
response from server: ab | ab

我想我们可以使用client.writeQuery来更新缓存,因为当mutate请求触发时,用户键入 optimisticResponse进行更新,但现在这段代码正在获取非常难以理解。

update函数中可能还有一种方法可以解决这个问题,但我还没有走得那么远。

帮助?

我对阿波罗很新,所以也许我错过了一些东西。是否有更好的方法来处理阿波罗的许多快速突变?

2 个答案:

答案 0 :(得分:1)

每个突变都会返回一个承诺,因此您可以通过跟踪最新突变来了解无序突变何时到达。

由于您接受用户输入的任何内容,这意味着用户的值是规范的,并且您并不真正需要乐观响应。您所做的只是确保服务器具有与您相同的价值。

所以我建议你跟踪Redux中的输入并添加一个触发突变的商店监听器(带去抖)。

如果确实需要跟踪服务器值,请使用计数器查看返回的变异是否为最后一个(在变异发送时存储++计数器值,并将该值与变异时的计数器值进行比较)返回)。

答案 1 :(得分:0)

在 2020 年发现此线程。我尝试使用 add debounce 方法解决该问题,到目前为止效果很好。

如原始问题所述,主要挑战是保持组件状态与 apollo 缓存同步。我引入了一个 useEffect 来在 props 更改时更新本地缓存。以下代码段是我创建的一个辅助挂钩,用于编辑需要与服务器同步的任务。请注意,必须使用 optimisticResponse 调用突变。当突变被触发时,一个新的任务对象从 apollo 缓存中返回。它取代了本地 cachedTask。稍后,当服务器响应返回时,apollo 足够聪明,可以确定它与乐观响应是同一个对象,因此本地缓存不会再次更新。如果您在网络事务中进行更改,由于此机制,您的更改不会被覆盖。

export function useNewConnectedTask(defaultTask) {
  const [cachedTask, setCachedTask] = useState(defaultTask);
  const [updateTask] = useUpdateTaskMutation();
  useEffect(() => {
    setCachedTask(defaultTask);
  }, [defaultTask]);
  const debouncedUpdateTask = useCallback(
    debounce((task) => {
      updateTask({
        variables: {
          data: task,
        },
        optimisticResponse: {
          __typename: 'Mutation',
          updateTask: task,
        },
      });
    }, 1000), [updateTask],
  );
  const setTask = useCallback(
    (task: TaskFieldsDetailedFragment) => {
      setCachedTask(task);
      debouncedUpdateTask(task);
    }, [setCachedTask, debouncedUpdateTask],
  );

  return [cachedTask, setTask];
}