Начинающий веб-разработчик здесь. Я использую Firebase в качестве бэкэнда, и у меня есть ситуация, когда мне нужно запросить БД для одного значения и использовать это возвращаемое значение для сопоставления в другом запросе к той же БД.
Вот мой код:
function dataLoad() {
var valueThatINeedToUseElsewhere
userReference.on('value',function(users){
var currentUser = firebase.auth().currentUser.uid
users.forEach(function(user){
if (user.key === currentUser) {
valueThatINeedToUseElsewhere = user.val().userName
console.info(valueThatINeedToUseElsewhere)
}
})
})
console.info(valueThatINeedToUseElsewhere)
}
В console.info в условии IF записывается правильное значение. Однако последний из приведенных выше console.info ничего не регистрирует, несмотря на то, что имя переменной объявлено вне функции 'on'. Почему это происходит? И как я могу на самом деле использовать данные вне функции включения? Мне нужно использовать его для выполнения аналогичного условия IF в другой операции извлечения данных.
Спасибо!



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


on () является асинхронным и немедленно возвращается, что означает, что в журнале консоли будет отображаться неопределенное значение. Обратный вызов, который вы передаете on (), будет запущен только тогда, когда будут доступны результаты, и нет гарантии, как быстро это произойдет. Вы должны использовать значение из асинхронного вызова только после его завершения - не пытайтесь создать блок кода, пока не будет завершен какой-либо асинхронный вызов.
Также рассмотрите возможность использования once () вместо on (), если вам нужно значение в определенном месте только один раз. Вы можете использовать его возвращенное обещание, чтобы связать некоторую дополнительную работу после того, как он загрузит нужные данные. Он также является асинхронным (как и все другие методы, возвращающие обещание).
Чтобы узнать больше о том, почему API Firebase асинхронны и что это означает для вашего приложения, прочитай этот блог.
Скорее всего, вы сейчас пытаетесь сделать что-то вроде этого:
var valueThatINeedToUseElsewhere;
dataLoad();
doSomethingWith(valueThatINeedToUseElsewhere);
К сожалению, это не работает, поскольку (как говорит Дуг) данные загружаются из Firebase асинхронно. К тому времени, когда вы пытаетесь использовать valueThatINeedToUseElsewhere, значение еще не загружено.
Проще всего в этом убедиться, если вы разместите несколько таких операторов журнала:
console.info("Before starting to load value")
userReference.on('value',function(users){
console.info("Loaded value")
})
console.info("After starting to load value")
Когда вы запускаете этот код, он печатает:
Before starting to load value
After starting to load value
Loaded value
Вероятно, это не тот порядок, которого вы ожидали. Но это прекрасно объясняет, почему вы не можете использоватьvalueThatINeedToUseElsewhere при попытке: он еще не загружен.
Лучший способ справиться с этой ситуацией, который я нашел, - это переосмыслить проблему. Ваш текущий код написан с логикой «сначала мы загружаем значение, затем мы что-то делаем со значением». Для асинхронной загрузки лучше оформить это как «сначала начните загрузку данных. Как только данные начали загружаться, сделайте с ними что-нибудь».
В коде, который выглядит так:
function dataLoad() {
userReference.once('value',function(users){
var currentUser = firebase.auth().currentUser.uid
users.forEach(function(user){
if (user.key === currentUser) {
var valueThatINeedToUseElsewhere = user.val().userName
doSomethingWith(valueThatINeedToUseElsewhere)
}
})
})
}
Как вы увидите, мы переместили код, которому нужны данные. в - обратный вызов, который запускается, когда данные доступны. Таким образом, вы можете быть уверены, что данные доступны при звонке в doSomethingWith. Я также изменил код, чтобы использовать once, что (как сказал Дуг) лучше для вашего варианта использования.
Необходимо предпринять еще два шага, чтобы повысить гибкость и производительность вашего кода.
Во-первых: в этом последнем обновлении вызов doSomethingWith жестко запрограммирован, что означает, что dataLoad() и doSomethingWith тесно связаны друг с другом. Часто лучше держать их подальше друг от друга. Для этого мы можем передать так называемую функцию обратного вызова в dataLoad, которую он затем вызывает, когда загружает данные. Это очень аналогично тому, что уже делает once(). Посмотрим на это на практике:
function dataLoad(callback) {
userReference.once('value',function(users){
var currentUser = firebase.auth().currentUser.uid
users.forEach(function(user){
if (user.key === currentUser) {
var valueThatINeedToUseElsewhere = user.val().userName
callback(valueThatINeedToUseElsewhere)
}
})
})
}
Не слишком разные. Но теперь вы можете вызвать dataLoad следующим образом:
dataLoad(doSomethingWith);
И с этим doSomethingWith будет вызываться с данными, как только данные будут загружены.
Последнее изменение - оптимизация. Ваш текущий код загружает всех пользователей, а вам нужен только один и вы знаете их ключ. Гораздо эффективнее загружать только этого конкретного пользователя с помощью:
function dataLoad(callback) {
var currentUser = firebase.auth().currentUser.uid
userReference.child(currentUser).once('value',function(users){
var valueThatINeedToUseElsewhere = user.val().userName
callback(valueThatINeedToUseElsewhere)
})
}
Ух ты! Спасибо за невероятно подробный ответ, Фрэнк! Просматривая пост Дуга на Medium, я понял, что моя проблема - 1,3,2. Ваш метод решает это правильно - плюс тот факт, что вы получаете ровно одного пользователя, идеально. Однако обратный вызов на самом деле является еще одним запросом к базе данных, который мне нужно выполнить. И снова, это будет возвращение одного значения, как указано выше. Я не уверен, как написать обратный вызов для другого удаления данных из Firebase. Не могли бы вы пролить свет на это?
Думаю, я разобрался. Я отвечу здесь своим решением, как только оно заработает. Еще раз спасибо за отличный ответ!
спасибо за быстрый ответ, Дуг - я пробовал использовать once (), но это действительно сбивало с толку. Сможете ли вы переписать указанную выше функцию примерно с помощью once () вместо on ()?