我有一个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;
}
我觉得我从根本上误解了异步/等待,但我不明白代码中有什么不正确-任何帮助或建议都将不胜感激。
答案 0 :(得分:1)
简而言之
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
答案 1 :(得分: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,因此通常的解决方案是return
或await
相关的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
创建新的Promises通常被认为是代码气味。通常,仅在包装不正确支持Promises的代码时才有必要。如果我们使用的是具有适当Promise支持的库(如此处所示),则无需创建任何Promises。
那么onAuthStateChanged
为什么不返回承诺?
因为这是观看所有登录/注销事件的一种方式。每次用户登录或注销时,都会调用回调。它并不是用来观看特定登录的方式。一个Promise只能被解析为一个值。因此,尽管可以使用Promise对单个登录事件进行建模,但在观看所有登录/退出事件时却毫无意义。
因此fetchCreds
正在注册,以通知所有登录/注销事件。它对返回的unsubscribe
函数不做任何事情,因此大概它将监听所有此类事件,直到重新加载页面为止。如果您多次拨打fetchCreds
,它将继续添加越来越多的侦听器。
如果您正在等待用户完成登录,那么建议您直接等待。 firebase.auth()
有多种以前缀signIn
开头的方法,例如signInWithEmailAndPassword
,并且它们确实会返回一个Promise,该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', {})
}
}
正如我已经提到的,这基于用户已经登录的假设。如果这不是一个安全的假设,那么您可能要考虑等到登录完成后再创建需要用户的组件凭据。
更新:
评论中的问题:
如果obj.method()调用是异步的,并且我们确实在其中等待回调函数,那是否可以确保外部异步函数(myFn)在内部函数完成之前从未解析?
我不确定您在这里问什么。
请清楚一点,我对async
和 asynchronous 这两个词的使用非常小心。诸如setTimeout
之类的函数将被视为异步,但不是async
。
async
/ await
只是Promises周围的许多语法糖。您并不是真正在等待功能,而是在等待Promise。当我们谈论等待async
函数时,我们实际上是在等待等待Promise返回解决。
因此,当您说等待回调函数时,并不清楚这意味着什么。您要尝试await
哪个承诺?
将async
修饰符放在函数上并不能使其神奇地等待事件。它只会在遇到await
时等待。您仍可以在async
函数中进行其他异步调用,并且与普通函数一样,这些调用将在函数返回后执行。 “暂停”的唯一方法是await
一个承诺。
将await
放入另一个函数(甚至是嵌套函数)中,除非外部函数已经在等待内部函数,否则对外部函数是否等待没有任何影响。在幕后,这仅仅是Promises链接的then
调用。每当您写await
时,您都只是向Promise添加了另一个then
调用。但是,除非Promise与外部async
函数返回的Promise处于同一链中,否则这将不会达到预期的效果。只需丢失一个链接即可使链失效。
因此,修改我之前的示例:
async function myFn (obj) {
await obj.method(async function () {
await somePromise
// ...
})
// ...
}
await myFn(x)
请注意,这里有3个函数:myFn
,method
和回调传递给method
。问题是,await myFn(x)
是否会等待somePromise
?
根据上面的代码,我们实际上无法分辨。这将取决于method
内部所做的事情。例如,如果method
看起来像这样,它将仍然无法工作:
function method (callback) {
setTimeout(callback, 1000)
}
将async
放在method
上无济于事,这只会使其返回一个Promise,但Promise仍然不会等待计时器触发。
我们的Promise链链接断开。 myFn
和回调函数都在创建链的各个部分,但是除非method
将这些Promises链接在一起,否则它将不起作用。
另一方面,如果编写method
来返回一个合适的Promise,它等待回调完成,那么我们将获得目标行为:
function method (callback) {
return someServerCallThatReturnsAPromise().then(callback)
}
我们可以在这里使用async
/ await
,但是没有必要,因为我们可以直接返回Promise。
此外,如果在异步myFn函数中您没有返回任何内容,是否意味着它将立即解析为未定义内容?
立即一词在这里定义不明确。
return undefined
。async
函数返回的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 4
和here 3
之间跳入here 5
。