Запускать действия Vuex асинхронно и последовательно - что я не понимаю? - PullRequest
0 голосов
/ 04 июля 2019

У меня есть магазин Vuex, и я пытаюсь получить данные из базы данных Firebase Realtime.Сначала я получаю информацию о пользователе, однако впоследствии я хотел бы получить другую информацию, которая зависит от исходных данных.

Как вы можете видеть из кода, я пытаюсь сделать это с помощью 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)
        }
    }
}

Методы, вызываемые в методе create () моего компонента.

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

У меня такое чувство, что я в корне неправильно понимаю async / await, но я не могу понять, что в коде неверно - любая помощь или совет будет принята с благодарностью.

Ответы [ 2 ]

1 голос
/ 04 июля 2019

Я не особо знаком с 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 вы ничего не возвращаете, значит ли это, что она разрешится немедленно и как неопределенная?

Термин немедленно здесь не совсем определен.

  1. Если функция ничего не возвращает в конце, то это эквивалентно return undefined в конце.
  2. Обещание, возвращаемое функцией async, разрешится в тот момент, когда функция возвращает.
  3. Разрешенным значением для 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.

1 голос
/ 04 июля 2019

Чтобы сделать его коротким

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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...