1

I'm developing an app using Vuejs and Vuex.

I've got a Vuex module called settings_operations. This module has the following action:

async changePassword ({ commit }, { password, id }) {
  commit(CHANGE_PASSWORD_PROCESSING, { id })
  const user = auth.currentUser
  const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
  if (changePasswordError) {
    commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
  } else {
    commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
  }
}

Edit: the to() is https://github.com/scopsy/await-to-js

With the following mutations:

[CHANGE_PASSWORD_PROCESSING] (state, { id }) {
  state.push({
    id,
    status: 'processing'
  })
},
[CHANGE_PASSWORD_ERROR] (state, { id, error }) {
  state.push({
    id,
    error,
    status: 'error'
  })
}

And then, in the component I want to use this state slice:

computed: {
  ...mapState({
    settings_operations: state => state.settings_operations
  })
},
watch: {
  settings_operations: {
    handler (newSettings, oldSettings) {
      console.log(newSettings)
    },
    deep: false
  }
}

The problem is that when the changePassword action results in an error, the watch doesn't stop in the PROCESSING step, it goes directly to the ERROR moment so the array will be filled with 2 objects. It literally jumps the "processing" watching step.

A funny thing that happens is that if I add a setTimeout just like this:

async changePassword ({ commit }, { password, id }) {
  commit(CHANGE_PASSWORD_PROCESSING, { id })

  setTimeout(async () => {
    const user = auth.currentUser
    const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
    if (changePasswordError) {
      commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
    } else {
      commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
    }
  }, 500)
},

It works! The watch stops two times: the first tick displaying the array with the processing object and the second tick displaying the array with 2 objects; the processing one and the error one.

What am I missing here?

Edit:

I reproduced the problem here: https://codesandbox.io/s/m40jz26npp

4

1 回答 1

0

这是一位核心团队成员在 Vue 论坛中给出的回复:

并非每次底层数据更改时都会运行观察程序。如果他们的观察数据至少改变一次,他们只会在下一个 Tick 上运行一次。

你在 try 块中被拒绝的 Promise 只是一个微任务,它不会将执行推送到下一个调用堆栈(观察者将在其上运行),因此错误处理发生在观察者运行之前。

此外,当您对对象或数组进行变异时,深度观察器中的 newValue 和 oldValue 将是相同的。请参阅文档:

Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the

相同的对象/数组。Vue 不保留 pre-mutate 值的副本。

作为最后的旁注,我从未见过有人使用 aray 作为模块的根状态,我不知道这是否适用于所有可能的情况下的 vuex。我当然不建议这样做。

使用来自同一成员的更好、更完整的答案进行编辑:

为什么观察者完全是异步的?因为在绝大多数用例中,观察者只需要对完成的最后同步更改做出反应。在大多数情况下(在组件的上下文中),对每个更改做出反应会适得其反,因为您会多次重新触发相同的行为,即使最终只有最后一个状态是重要的。

换句话说:默认情况下对每个更改运行一个观察程序可能会导致应用程序消耗大量 CPU 周期做无用的工作。所以观察者是用一个只在 nexTick 上刷新的异步队列实现的。然后我们不允许重复的观察者,因为一旦队列被刷新,观察者的旧实例将应用于在该状态下不再“存在”的数据。

一个重要的注意事项是,这仅适用于同步更改或在微任务中完成的更改,即立即解决或失败的承诺 - 例如,它不会发生在 ajax 请求中。

为什么它们以一种在微任务之后仍然不运行的方式实现(即立即解决的承诺?这有点复杂,需要一些历史来解释。

最初,在 Vue 2.0 中,Vue.nextTick 本身被实现为一个微任务,并且观察者队列在 nextTick 上刷新。这意味着在当时,一个观察者观察一段被改变了两次的数据,中间有一个微任务(比如一个承诺),确实会运行两次。

然后,我认为在 2.4 左右,我们发现了这个实现的一个问题,并将 Vue.nextTick 切换为一个宏任务。在这种行为下,两个数据改变都将发生在当前调用堆栈的 microtaks 队列中,而观察者队列将在下一个调用堆栈的开头刷新,这意味着它只会运行一次。

我们发现这个实现的几个新问题比微任务的原始问题更常见,因此我们可能会在 2.6 中切换回微任务实现。丑陋,但必要。

所以,现在这应该可以解决问题:

await Vue.nextTick();
于 2018-07-20T09:48:22.577 回答