【问题标题】:Apollo mutation debounce and race conditions阿波罗突变去抖动和竞争条件
【发布时间】:2018-01-22 20:00:12
【问题描述】:

(这是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

现在服务器在graphql中记录了“a”,这是不正确的。

添加去抖动

为了缓解这种情况,我在按键事件中添加了去抖动。尽管这确实有助于解决上述竞争条件,但并不能解决它。如果网络速度低于您的去抖动阈值,仍然可能出现竞争条件。

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

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

添加网络队列

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

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 来更新到“abc”,直到“send mutate request:abc”。

这不是什么大不了的事,只是延迟,但似乎我们应该能够做到。

尝试在用户输入时更新缓存

在文档中,我了解到我可以使用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 来更新缓存,因为用户键入optimisticResponse 以在变异请求触发时进行更新,但现在这段代码变得很难理解.

update 函数中也可能有办法处理这个问题,但我还没有走那么远。

帮助?

我对 Apollo 还很陌生,所以也许我遗漏了一些东西。有没有更好的方法来处理阿波罗的许多快速突变?

【问题讨论】:

    标签: reactjs graphql apollo react-apollo


    【解决方案1】:

    在 2020 年发现了这个帖子。我尝试用 add debounce 方法解决这个问题,到目前为止效果很好。

    如原始问题中所述,主要挑战是使组件状态与 apollo 缓存保持同步。我引入了一个 useEffect 来更新 props 更改的本地缓存。下面的 sn-p 是我创建的一个帮助钩子,用于编辑需要与服务器同步的任务。请注意,必须使用optimisticResponse 调用突变。当突变被触发时,从 apollo 缓存中返回一个新的任务对象。它取代了本地缓存任务。稍后,当服务器响应返回时,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];
    }

    【讨论】:

      【解决方案2】:

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

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

      所以我建议您在 Redux 中跟踪输入并添加一个触发突变(带去抖动)的存储侦听器。

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

      【讨论】:

        猜你喜欢
        • 2019-04-02
        • 2017-03-23
        • 2019-02-20
        • 2021-01-24
        • 1970-01-01
        • 2019-02-08
        • 2019-04-16
        • 2017-09-04
        • 1970-01-01
        相关资源
        最近更新 更多