0

The below Cloud Function only returns the first data instead of 10:

exports.viewdata = functions.https.onRequest((req, res) => {
    const userId = req.query.user;
    return admin.database().ref('users/' + userId)
    .orderByKey()
    .limitToLast(10)    
    .on('child_added', function(snapshot) {
        snapshot.forEach(function(data) {
        res.status(200).send(data.val());
        });
     });
});

Please help me to fix this.

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
Fresco
  • 343
  • 3
  • 13

1 Answers1

2

Using send(), end() or redirect() will terminate a HTTP Cloud Function:

Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system.

In your example, you are calling res.status(200).send(data.val()); inside your forEach iteration of each child snapshot, therefore it will only get a chance to send the one response.

Likewise, because you have used a child_added event listener, it is triggered once for every child at the specified path.

If you need to respond with all the query data at once, you'll be better off with a value event listener instead, which will retrieve all data from your query in a single response:

exports.viewdata = functions.https.onRequest((req, res) => {
    const userId = req.query.user;
    admin.database().ref('users/' + userId)
    .orderByKey()
    .limitToLast(10)    
    .on('value', function(snapshot) {
        res.status(200).send(snapshot.val());
    });
});

However, if your intention is to build a response with each child separately, you could use res.write() to write data to the response, and then eventually send this with end():

exports.viewdata = functions.https.onRequest((req, res) => {
    const userId = req.query.user;
    admin.database().ref('users/' + userId)
    .orderByKey()
    .limitToLast(10)    
    .on('value', function(snapshot) {
        snapshot.forEach(function(data) {
            res.write(data.val());
        });
        res.status(200).end();
    });
});

Or, you could add them to a list before sending them all back as a response. The method you take here all depends on your end-goal though.


Unrelated to your initial question, but for completeness, please see the below observations and side notes, from comments:

  • The return statement for admin.database().ref() is not required for HTTPS triggers as they have a different lifecycle than other triggers and do not need to return promises.

  • If you have no need to listen for further changes after you've retrieved the necessary data, you should consider using once() (to read data once) instead of on() or removing the on() listener with off().

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
  • Almost. A few more things are needed to make this work: 1) listen for the `value` event, so that the callback gets all 10 children at once. 2) either build the result using a `forEach` **or** send the 10 results in one go with `snapshot.val()`. – Frank van Puffelen Nov 16 '17 at 15:01
  • @FrankvanPuffelen Ah yeah, I missed the `child_added` part! Thanks Frank, I'll update the answer. – Grimthorr Nov 16 '17 at 15:17
  • There's no need for the `return` here, since the return value from an HTTPS function doesn't mean anything (also, on() doesn't even return a promise or anything useful like that). – Doug Stevenson Nov 16 '17 at 15:58
  • Also consider removing the listener with `off()` so that it doesn't continue to listen after the function returns - that could be wasteful. – Doug Stevenson Nov 16 '17 at 16:00
  • @DougStevenson Thanks you're right, I [answered another similar question](https://stackoverflow.com/a/47328078/2754146) earlier today where dropping the `return` is necessary. Also, using `once()` would be better here to avoid leaving the listener attached. – Grimthorr Nov 16 '17 at 16:15