为啥 Firebase 会在 once() 函数之外丢失引用?

Posted

技术标签:

【中文标题】为啥 Firebase 会在 once() 函数之外丢失引用?【英文标题】:Why Does Firebase Lose Reference outside the once() Function?为什么 Firebase 会在 once() 函数之外丢失引用? 【发布时间】:2017-04-02 23:09:35 【问题描述】:

我正在使用 Firebase 和 angularJS 来获取用户列表。我可以使用once() 函数从我的数据库中读取所有用户,但我不知道为什么userList 在下面返回未定义

.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 方面遗漏了,我在文档中找不到它;有人可以帮我理解我的问题吗?

【问题讨论】:

【参考方案1】:

问题是(正如 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");
    
]);

这个输出将是:

在附加监听器之前

附加监听器后

获得价值

知道它的执行顺序应该可以完美地解释为什么在您刚刚附加监听器后无法打印用户列表。如果没有,我建议您也阅读这个很棒的答案:How do I return the response from an asynchronous call?

现在解决方案:您将要么需要在回调中使用用户列表要么返回一个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

返回一个承诺

您通常不希望将所有需要用户的代码放入getUsers() 函数中。在这种情况下,您可以或者将回调传递给getUsers()(我不会在这里展示,但它与您可以传递给once()的回调非常相似)或者 em> 你可以从getUsers()返回一个承诺:

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() 并使用生成的承诺在用户加载后获取他们:

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

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

【讨论】:

【参考方案2】:

你需要考虑异步:

.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 );
    
]);

根据 Franks 的评论进行澄清:

我应该进一步澄清setTimeout 只是证明这是一个时间问题。如果您在应用程序中使用 setTimeout,您可能会遇到很糟糕的情况,因为您无法可靠地等待 n 毫秒以获得结果,您需要获取它们,然后在获得快照后触发回调或解决承诺数据。

【讨论】:

感谢@imjared,我添加了一些额外的逻辑,以确保在我的异步函数在上面的代码中成功完成后填充userList setTimeout() 可能有效也可能无效(主要取决于您的网络延迟),但它从不解决此问题。正确的解决方案是将需要users列表的代码移到callback/then块中。

以上是关于为啥 Firebase 会在 once() 函数之外丢失引用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥只有在部署到 Firebase 时才会在 Node.js 和 Angular 之间出现 CORS 错误?

为啥函数require_once会阻止我的代码工作[关闭]

有人可以指出为啥我的 require_once() 函数找不到路径吗? php

Android:为啥firebase Push键不改变

为啥 require_once 在以下情况下不起作用?

为啥调用firebase函数时会出现FCM错误