【问题标题】:Firing Vuex actions asynchronously and sequentially - what am I not understanding?异步和按顺序触发 Vuex 操作 - 我不明白什么?
【发布时间】:2019-11-14 15:53:59
【问题描述】:

我有一个 Vuex 商店,我正在尝试从 Firebase 实时数据库中获取数据。我最初是在获取用户信息,但之后我想获取一些依赖于获取的初始数据的其他信息。

正如您从代码中看到的那样,我正在尝试使用 async / await 来执行此操作,但是每当在我的 created() 挂钩中触发这两个操作时,用户的信息都不会被初始化,因此第二个操作会失败。

我的用户商店

    async fetchCreds({ commit }) {
        try {
            firebase.auth().onAuthStateChanged(async function(user) {
                const { uid } = user
                const userDoc = await users.doc(uid).get()

                return commit('SET_USER', userDoc.data())
            })
        } catch (error) {
            console.log(error)
            commit('SET_USER', {})
        }
    }

我的俱乐部行动依赖于上述调用

    async fetchClubInformation({ commit, rootState }) {
        try {
            const clubIDForLoggedInUser = rootState.user.clubId
            const clubDoc = await clubs.doc(clubIDForLoggedInUser).get()

            return commit('SET_CLUB_INFO', clubDoc.data())
        } catch (error) {
            console.log(error)
        }
    }
}

在我的组件的 created() 方法中调用的方法。

  created: async function() {
        await this.fetchCreds();
        await this.fetchClubInformation();
        this.loading = false;
  }

我感觉我从根本上误解了 async / await,但我无法理解代码中的错误 - 任何帮助或建议将不胜感激。

【问题讨论】:

  • 你得到了什么结果?浏览器控制台中的任何错误?网络控制台在请求方面显示什么? Vuex 选项卡中 chrome 的 Vue 插件显示什么?
  • 另外,你能展示一下你是如何映射动作的吗?
  • 抱歉,我最初应该包含更多信息。我得到的错误是来自 Firebase 的错误,但它与我作为文档 ID 传递的变量未定义这一事实有关,即使我只是使用初始操作设置它。就我如何映射动作而言,我使用了 Vue 的 mapActions 方法,然后在 created() 钩子中调用它们。
  • fetchcreds 返回 undefined - 无需等待解决
  • @Estradiaz 所以,如果我只是在 try catch 语句下返回一些东西,调用会按预期工作吗?

标签: javascript firebase vue.js firebase-realtime-database vuex


【解决方案1】:

我对 Firebase 不是特别熟悉,但在深入研究了源代码之后,我想我可以对您的问题有所了解。

首先,考虑下面的例子:

async function myFn (obj) {
  obj.method(function () {
    console.log('here 1')
  })

  console.log('here 2')
}

await myFn(x)
console.log('here 3')

问题:您会按什么顺序查看日志消息?

here 2 肯定会出现在 here 3 之前,但是从上面的代码中无法判断 here 1 何时会出现。这取决于obj.method 对传递的函数做了什么。它可能永远不会调用它。它可能会同步调用它(例如 Array 的 forEach 方法),在这种情况下,here 1 将出现在其他消息之前。如果它是异步的(例如计时器、服务器调用),那么here 1 可能会在一段时间内不会出现,在here 3 之后很久。

如果 async 修饰符本身不返回 Promise,它将从函数中隐式返回 Promise。该 Promise 的解析值将是函数返回的值,Promise 将在函数返回时解析。对于末尾没有return 的函数,它相当于以return undefined 结尾。

所以,为了强调重点,异步函数返回的 Promise 只会等到该函数返回。

方法onAuthStateChanged 异步调用它的回调,因此该回调中的代码在周围的函数完成之前不会运行。没有什么可以告诉隐式返回的 Promise 等待调用该回调。回调中的 await 无关紧要,因为该函数甚至还没有被调用。

Firebase 广泛使用 Promise,因此通常解决方案是向 returnawait 发送相关的 Promise:

// Note: This WON'T work, explanation follows
return firebase.auth().onAuthStateChanged(async function(user) {
// Note: This WON'T work, explanation follows
await firebase.auth().onAuthStateChanged(async function(user) {

这在此处不起作用,因为 onAuthStateChanged 实际上并没有返回 Promise,它返回的是 unsubscribe 函数。

当然,您可以自己创建一个新的 Promise 并以这种方式“修复”它。但是,使用new Promise 创建新的 Promise 通常被认为是代码异味。通常只有在包装不支持 Promises 的代码时才需要。如果我们正在使用具有适当 Promise 支持的库(就像我们在这里一样),那么我们不需要创建任何 Promise。

那么为什么onAuthStateChanged 不返回一个 Promise?

因为这是一种查看所有登录/退出事件的方式。每次用户登录或注销时,它都会调用回调。它不是用来观看特定登录的方式。一个 Promise 只能被解析一次,一个值。因此,虽然可以使用 Promise 对单个登录事件进行建模,但在查看所有登录/注销事件时是没有意义的。

所以fetchCreds 正在注册以接收有关所有登录/注销事件的通知。它不会对返回的 unsubscribe 函数做任何事情,因此它可能会监听所有此类事件,直到页面重新加载。如果您多次致电fetchCreds,它将不断添加越来越多的听众。

如果您正在等待用户完成登录,那么我建议您直接等待。 firebase.auth() 有各种以前缀 signIn 开头的方法,例如signInWithEmailAndPassword,并且这些确实会返回一个 Promise,当用户完成登录时会解析。解析的值提供对各种信息的访问,包括用户。我不知道您使用的是哪种方法,但所有方法的想法都差不多。

但是,您可能真的只是对获取当前用户的详细信息感兴趣。如果这就是你想要的,那么你根本不需要使用onAuthStateChanged。您应该可以使用 currentUser 属性获取副本。像这样的:

async fetchCreds({ commit }) {
   try {
      const { uid } = firebase.auth().currentUser
      const userDoc = await users.doc(uid).get()

      commit('SET_USER', userDoc.data())
   } catch (error) {
      console.log(error)
      commit('SET_USER', {})
   }
}

正如我已经提到的,这依赖于用户已经登录的假设。如果这不是一个安全的假设,那么您可能需要考虑等到登录完成后再创建需要用户的组件凭据。

更新:

来自 cmets 的问题:

如果 obj.method() 调用是异步的,并且我们确实在其中等待回调函数,这是否会确保外部异步函数 (myFn) 在内部异步函数完成之前永远不会解析?

我不完全确定你在这里问什么。

为了清楚起见,我在使用 async异步 这两个词时非常小心。 setTimeout 之类的函数将被视为异步,但它不是async

async/await 只是围绕 Promise 的大量语法糖。你并不是真的在等待一个函数,而是在等待一个 Promise。当我们谈论等待 async 函数时,我们实际上是在谈论等待它返回解决的 Promise。

因此,当您说 等待回调函数 时,并不清楚这意味着什么。你想await哪个承诺?

async 修饰符放在函数上不会让它神奇地等待事情。它只会在遇到await 时等待。您仍然可以在 async 函数中进行其他异步调用,并且与普通函数一样,这些调用将在函数返回后执行。 “暂停”的唯一方法是 await 一个 Promise。

await 放在另一个函数中,即使是嵌套函数,也不会影响外部函数是否等待,除非外部函数已经在等待内部函数。在幕后,这只是链接then 调用的承诺。每当您编写 await 时,您只是在向 Promise 添加另一个 then 调用。但是,除非 Promise 与外部 async 函数返回的 Promise 位于同一链中,否则这不会产生预期的效果。只需缺少一个链接,链条就会失效。

所以修改我之前的例子:

async function myFn (obj) {
  await obj.method(async function () {
    await somePromise

    // ...
  })

  // ...
}

await myFn(x)

请注意,这里有 3 个函数:myFnmethod 和传递给 method 的回调。问题是,await myFn(x) 会等到somePromise 吗?

从上面的代码我们实际上无法判断。这将取决于 method 在内部做什么。例如,如果method 看起来像这样,那么它仍然不起作用:

function method (callback) {
  setTimeout(callback, 1000)
}

async 放在method 上不会有任何帮助,这只会让它返回一个 Promise,但 Promise 仍然不会等待计时器触发。

我们的 Promise 链有一个断开的链接。 myFn 和回调都在创建它们的链部分,但除非 method 将这些 Promise 链接在一起,否则它将无法工作。

另一方面,如果 method 被编写为返回一个合适的等待回调完成的 Promise,那么我们将获得目标行为:

function method (callback) {
  return someServerCallThatReturnsAPromise().then(callback)
}

我们本可以在这里使用async/await,但没有必要,因为我们可以直接返回 Promise。

另外,如果在 async myFn 函数中您没有返回任何内容,这是否意味着它会立即解析并且未定义?

立即这个词在这里没有很好的定义。

  1. 如果一个函数最后没有返回任何东西,那么它就相当于最后有return undefined
  2. async 函数返回的 Promise 将在函数返回时解析。
  3. Promise 的解析值将是返回的值。

因此,如果您不返回任何内容,它将解析为 undefined。在到达函数末尾之前,不会发生解析。如果该函数不包含任何 await 调用,那么这将“立即”发生,与返回“立即”的同步函数相同。

但是,await 只是围绕then 调用的语法糖,而then 调用始终是异步的。因此,尽管 Promise 可能解决“立即”,await 仍然需要等待。这是一个很短的等待时间,但它不是同步的,其他代码可能有机会在此期间运行。

考虑以下几点:

const myFn = async function () {
  console.log('here 3')
}

console.log('here 1')

Promise.resolve('hi').then(() => {
  console.log('here 4')
})

console.log('here 2')

await myFn()

console.log('here 5')

日志消息将按照它们的编号顺序显示。所以即使myFn“立即”解析,你仍然会得到here 4here 3here 5之间跳转。

【讨论】:

  • 非常非常感谢您的详细回答,它确实帮助我更清楚地理解了事情,感谢您抽出宝贵时间深入了解。不过,我确实有一个问题,如果 obj.method() 调用是异步的,并且我们确实在其中等待回调函数,那会确保外部异步函数(myFn)在内部异步函数完成之前永远不会解析吗?此外,如果在 async myFn 函数中您没有返回任何内容,这是否意味着它会立即解析并且未定义?还是我误会了?
  • @Joshua 我在末尾添加了一个新部分来解决您的问题。我建议你自己尝试一些简单的例子来帮助你加深理解。然后阅读 MDN 对 Promises 和 async/await 的介绍。即使你以前读过它们,也要再读一遍。它需要进行几次迭代才能融入其中。
  • 谢谢。这让事情变得很清楚。我想我仍然需要花几个小时来掌握它,但正如你所说,摆弄代码应该会有所帮助。再次感谢您的深入回答。我真的很感激。
【解决方案2】:

简而言之

fetchCreds({ commit }) {
    return new Promise((resolve, reject) => {
    try {
        firebase.auth().onAuthStateChanged(async function(user) {
            const { uid } = user
            const userDoc = await users.doc(uid).get()

            commit('SET_USER', userDoc.data())
            resolve()
        })
    } catch (error) {
        console.log(error)
        commit('SET_USER', {})
        resolve()
    }}
}

async () => undefined // returns Promise<undefined> -> undefined resolves immediatly

asnyc () => func(cb) // returns Promise<any> resolves before callback got called

() => new Promise(resolve => func(() => resolve())) // resolves after callback got called 

【讨论】:

    猜你喜欢
    • 2020-09-14
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 1970-01-01
    • 2018-04-28
    • 2022-01-05
    • 1970-01-01
    相关资源
    最近更新 更多