Я не особо знаком с 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
делает с переданной функцией. Это может никогда не назвать это вообще. Он может вызывать его синхронно (например, метод forEach
массива), и в этом случае here 1
появится перед другими сообщениями. Если он асинхронный (например, таймеры, серверные вызовы), то here 1
может не отображаться в течение некоторого времени, намного позже here 3
.
Модификатор async
будет неявно возвращать Promise из функции, если он сам не возвращает Promise. Разрешенным значением этого Обещания будет значение, возвращаемое функцией, и Обещание будет разрешено в тот момент, когда функция возвращает . Для функции без return
в конце, эквивалентной завершению с return undefined
.
Итак, чтобы подчеркнуть ключевой момент, Promise, возвращаемый асинхронной функцией, будет только ждать, пока эта функция вернется.
Метод onAuthStateChanged
вызывает свой обратный вызов асинхронно, поэтому код в этом обратном вызове не будет выполняться до тех пор, пока не будет завершена окружающая функция. Нет ничего, что могло бы сказать неявно возвращенному Обещанию дождаться вызова этого обратного вызова. await
внутри обратного вызова не имеет значения, так как эта функция даже еще не была вызвана.
Firebase широко использует Обещания, поэтому обычно решением будет просто return
или await
соответствующего Обещания:
// 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
функцию.
Конечно, вы могли бы создать новое Обещание самостоятельно и «починить» его таким образом. Однако создание новых Обещаний с использованием new Promise
обычно считается запахом кода. Обычно это необходимо только при переносе кода, который не поддерживает обещания должным образом. Если мы работаем с библиотекой, которая имеет надлежащую поддержку Promise (как и мы здесь), нам не нужно создавать какие-либо Обещания.
Так почему же onAuthStateChanged
не возвращает обещание?
Потому что это способ просмотра всех событий входа / выхода. Каждый раз, когда пользователь входит или выходит, он вызывает обратный вызов. Он не предназначен для просмотра определенного входа. Обещание может быть разрешено только один раз, к одному значению. Поэтому, хотя одно событие входа можно смоделировать с помощью Promise, оно не имеет смысла при просмотре всех событий входа / выхода.
Итак, fetchCreds
регистрируется, чтобы получать уведомления обо всех событиях входа / выхода. Он ничего не делает с возвращенной функцией unsubscribe
, поэтому, вероятно, он будет прослушивать все такие события, пока страница не будет перезагружена. Если вы звоните fetchCreds
несколько раз, он будет добавлять все больше и больше слушателей.
Если вы ждете, пока пользователь завершит вход, я предлагаю вместо этого подождать этого. firebase.auth()
имеет различные методы, начинающиеся с префикса signIn
, например, signInWithEmailAndPassword
, и они возвращают Обещание, которое разрешается, когда пользователь завершил вход в систему. Разрешенное значение обеспечивает доступ к различной информации, включая пользователя. Я не знаю, какой метод вы используете, но идея для них одинакова.
Однако, возможно, вы действительно просто хотите узнать подробности текущего пользователя. Если это все, что вы хотите, вам не нужно использовать 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
- это просто много синтаксического сахара вокруг Обещаний. Вы действительно не ждете функции, вы ждете обещание. Когда мы говорим об ожидании функции async
, мы на самом деле говорим о том, чтобы дождаться обещания, которое оно вернуло к разрешению.
Поэтому, когда вы говорите , ждите функцию обратного вызова , не совсем понятно, что это значит. Какое обещание вы пытаетесь await
?
Помещение модификатора async
в функцию не заставляет ее волшебным образом ждать чего-либо. Он будет только ждать, когда встретит await
. Вы можете по-прежнему иметь другие асинхронные вызовы в функции async
, и, как и в случае с обычной функцией, эти вызовы будут выполняться после возврата из функции. Единственный способ сделать паузу - это await
Обещание.
Помещение await
в другую функцию, даже вложенную функцию, не будет иметь никакого значения для того, ожидает ли внешняя функция, если внешняя функция уже не ожидает внутреннюю функцию. За кулисами это всего лишь обещания, приковывающие then
звонки. Всякий раз, когда вы пишете await
, вы просто добавляете еще один вызов then
к Обещанию. Однако это не даст желаемого эффекта, если только это обещание не находится в той же цепочке, что и обещание, возвращаемое внешней функцией async
. Для сбоя цепочки требуется только одна ссылка.
Так изменив мой предыдущий пример:
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 разорвана ссылка. myFn
и обратный вызов создают свои части цепочки, но если method
не свяжет эти Обещания вместе, это не сработает.
С другой стороны, если написано method
для возврата подходящего Обещания, которое ожидает завершения обратного вызова, мы получим наше целевое поведение:
function method (callback) {
return someServerCallThatReturnsAPromise().then(callback)
}
Мы могли бы использовать здесь async
/ await
, но в этом не было необходимости, поскольку мы могли просто вернуть Обещание напрямую.
Кроме того, если в асинхронной функции myFn вы ничего не возвращаете, значит ли это, что она разрешится немедленно и как неопределенная?
Термин немедленно здесь не совсем определен.
- Если функция ничего не возвращает в конце, то это эквивалентно
return undefined
в конце.
- Обещание, возвращаемое функцией
async
, разрешится в тот момент, когда функция возвращает.
- Разрешенным значением для Promise будет возвращаемое значение.
Так что, если вы ничего не вернете, оно разрешится на undefined
. Разрешение не произойдет, пока не будет достигнут конец функции. Если функция не содержит вызовов await
, это произойдет «немедленно» в том же смысле, что и синхронная функция, возвращающая «немедленно».
Однако, await
- это просто синтаксический сахар вокруг вызова then
, а вызовы then
всегда асинхронны.Поэтому, хотя Обещание может разрешить 'немедленно', 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
.