为什么 Firebase 在 once() 函数之外丢失引用?

IT技术 javascript angularjs firebase firebase-realtime-database
2021-02-05 20:28:43

我正在使用 Firebase 和 angularJS 来获取用户列表。我可以使用该once()函数从我的数据库中读取所有用户,但我无法弄清楚为什么 userList在下面返回 undefined

.service('userService', [function() {
    this.getUsers = function() {
        var users;
        var userList;
        var ref = firebase.database().ref('/users/');
        ref.once('value').then(function(snapshot) {
            users = snapshot.val();
            for(var key in users) {
                users[key].id = key;
                // do some other stuff
            }
            console.log(users); // outputs all users
        }).then(function(){
            userList = users; 
            console.log(userList); // outputs all users
        },(function(error){
            alert('error:  ' + error);
        });
        console.log(userList); // outputs 'undefined'
    }
}]);

我等待分配我的userList变量,直到我完成处理users,但没有运气。

这里有一些重要的东西,我在 Promises/callbacks 方面遗漏了,我在文档中找不到它;有人可以帮助我理解我的问题吗?

2个回答

问题是(正如 imjared 所说)数据是从 Firebase 异步读取的。所以代码不会按照你想的顺序执行。通过仅使用几个日志语句对其进行简化,最容易看出:

.service('userService', [function() {
    this.getUsers = function() {
        var ref = firebase.database().ref('/users/');
        console.log("before attaching listener");
        ref.once('value').then(function(snapshot) {
            console.log("got value");
        });
        console.log("after attaching listener");
    }
}]);

输出将是:

在附加侦听器之前

附加监听器后

得到了value

了解它的执行顺序应该可以完美地解释为什么在您刚刚附加侦听器后无法打印用户列表。如果没有,我还建议您阅读这个很棒的答案:如何从异步调用返回响应?

现在的解决方案:你要么需要使用用户列表回调返回的Promise。

在回调中使用用户列表

这是处理异步代码的最古老的方法:将所有需要用户列表的代码移动到回调中。

    ref.once('value', function(snapshot) {
        users = snapshot.val();
        for(var key in users) {
            users[key].id = key;
        }
        console.log(users); // outputs all users
    })

您将代码从“首先加载用户列表,然后打印其内容”重构为“每当加载用户列表时,打印其内容”。定义上的差异很小,但突然间您就完全具备了处理异步加载的能力。

你也可以用 promise 做同样的事情,就像你在代码中所做的那样:

   ref.once('value').then(function(snapshot) {
        users = snapshot.val();
        for(var key in users) {
            users[key].id = key;
            // do some other stuff
        }
        console.log(users); // outputs all users
    });

但是使用 promise 比直接使用回调有一个巨大的优势:你可以返回一个 promise

返回一个Promise

通常,您不想将需要用户的所有代码都放入getUsers()函数中。在这种情况下,你可以传递一个回调到getUsers()(我不会在这里展示,但它是非常相似的回调,你可以进入once()),可以从返回一个PromisegetUsers()

this.getUsers = function() {
    var ref = firebase.database().ref('/users/');
    return ref.once('value').then(function(snapshot) {
        users = snapshot.val();
        for(var key in users) {
            users[key].id = key;
            // do some other stuff
        }
        return(users);
    }).catch(function(error){
        alert('error:  ' + error);
    });
}

使用此服务,我们现在可以调用getUsers()并使用生成的 promise 在用户加载后获取它们:

userService.getUsers().then(function(userList) {
    console.log(userList);
})

这样你就驯服了异步野兽。嗯……至少现在是这样。即使是经验丰富的 JavaScript 开发人员,这也会偶尔让他们感到困惑,所以如果需要一些时间来适应,请不要担心。

你需要考虑异步:

.service('userService', [function() {
    this.getUsers = function() {
        var users;
        var ref = firebase.database().ref('/users/');

        <!-- this happens ASYNC -->
        ref.once('value', function(snapshot) {
            users = snapshot.val();
            for(var key in users) {
                users[key].id = key;
                // do some other stuff
            }
            console.log(users); // outputs all users
        },function(error){
            alert('error:  ' + error);
        });
        <!-- end async action -->

        console.log(users); // outputs 'undefined'
    }
}]);

我敢打赌,如果你这样做,你会没事的:

.service('userService', [function() {
    this.getUsers = function() {
        var users;
        var ref = firebase.database().ref('/users/');

        <!-- this happens ASYNC -->
        ref.once('value', function(snapshot) {
            users = snapshot.val();
            for(var key in users) {
                users[key].id = key;
                // do some other stuff
            }
            console.log(users); // outputs all users
        },function(error){
            alert('error:  ' + error);
        });
        <!-- end async action -->

        window.setTimeout( function() {
            console.log(users); // outputs 'undefined'
        }, 1500 );
    }
}]);

根据弗兰克斯的评论澄清:

我应该进一步澄清,setTimeout这只会证明这是一个时间问题。如果你在你的应用程序中使用 setTimeout,你可能会度过一段糟糕的时光,因为你不能可靠地等待 n 毫秒的结果,你需要得到它们然后在你获得快照后触发回调或解决Promise数据。

谢谢@imjared,我userList在上面的代码中包含了一些额外的逻辑以确保在异步函数成功完成后填充
2021-03-25 20:28:43
setTimeout()可能或可能无法正常工作(在你的网络延迟取决于居多),但从来没有为这个问题的解决方案。正确的解决方案是将需要users列表的代码移动到 callback/then 块中。
2021-03-31 20:28:43