Get array from server side to client side


#1

Hey guys,

I’m developing an app where besides having the usual collections with the publishes and meteor.methods, I also am using a google API to receive some email information.

That is already working OK. The thing is I don’t want this logic to be visible from the client’s perspective, so I’m doing this logic server side.

The roadblock at this moment is that when I use the Meteor.call and specify the method I created, I’m not receiving neither data or errors in the callback function.

From the server side perspective, what I’m doing is the return of an obj array containing the emails result.

Is this not the best way to implement this? Any thoughts or tips would be greatly appreciated.

Thank you in advance.

Cheers


#2

Sounds like it’s probably an async logic issue. Can you post your method code so we can figure out where the issue is?


#3

Hi, coagmano,

Most definitely, thank in advance for your prompt reply.

The code is quite extensive, but in a nutshell is something like this:

Meteor.methods({
     'fetchMessages'() {
         var messages = []
         // somehow populate messages;
         console.log("this just ran!");
         return messages;
     }
});

On the client side:

import '../../api/messages.js';
// definition of the controller and what not
fetchMail() {
    Meteor.call('fetchMessages', function(err, data) {
    // do stuff
    });
}

Hope this helps getting the picture, but I’ll be available to share more detail if necessary.

Thanks for the help!

Cheers


#4

That looks okay in principle. Can you tell us which folders the method, the client code and the messages.js file are in?


#5

Hey, robfallows,

Sure, I’ll attach a screen here:
image

Let me know if this helps.

Appreciated!


#6

As you may have noticed, I’ve based myself on the initial tutorial. I’m pretty new to meteor :slight_smile:


#7

Hmm. Still pretty confused.

  • Is your fetchMessages method in /imports/api/messages.js?
  • Your “client side” is /imports/components/todosList/todosList.js?
  • Where in your server code do you include your fetchMessages method?
  • When you say “I’m not receiving neither data or errors in the callback function”, how have you tested this?

#8

Hello, robfallows,

Sorry for the delay on my reply. I’ll try to address each one of your questions:

  1. Yes, that is correct. The fetchMessages methods is ins messages.js:
  2. This is also true. This is where I have the function being called from through a button in the view:
fetchMail() {
        Meteor.call('fetchMessages', function(err, data) {
  1. In server/main.js I’ve got the following:
import { Meteor } from 'meteor/meteor';
import '../imports/api/tasks.js';
import '../imports/api/messages.js';

Meteor.startup(() => {
  // code to run on server at startup
});
  1. Through the meteor.call that is itself called in side the function fetchMail inside the controller mentioned in 2.:
<p>
    <button ng-click="$ctrl.fetchMail()">fetch mail</button>
</p>

I’m sorry if I haven’t been very clear so far. I may also upload this to a bitbucket or something similar if necessary.

Thank you again for your help.

Kind regards


#9

I’m pretty sure the issue will be in here:

so yes we need the extensive code

I’m guessing you’re doing something with a callback and filling messages with the result?


#10

Hey, coagmano,

You’re right, inside the GoogleApi.get callback I’m pushing the messages into an array and returning it:

if (Meteor.isServer) {
    Meteor.methods({
        'fetchMessages'() {
            console.log("this just ran!");

            user = Meteor.users.findOne(Meteor.userId());
            email = user.services.google.email;

            let url = '/gmail/v1/users/' + encodeURIComponent(email) + '/messages?q=is:unread';
            GoogleApi.get(url, function (error, data) {
            (...)
            messages.push(message);
            (...)
            return messages;
            });
        }
});

This is pretty much it. I’ll proceed with trying to make this available meanwhile.

Cheers!


#11

Hey all,

As previously mentioned, and in order to facilitate the conversation, I’m sharing the code in this bitbucket:

Hopefully, this will make everything more clear :slight_smile:

Thanks again for your input so far!

Kind regards


#12

One additional note, if you run the code you may not obtain any mails, but I’ve tested the code while running on the client first and it was returning the intended array. It’s just a matter of getting it to the client side now.


#13

Okay so I’m not sure how familiar you are with callbacks and asynchronous code, but the general flow of your function currently is:

** Method comes in
create messages array
>> Send aync function request
** return empty messages array to client -> client sees empty array
<< async function finishes and runs callback, messages are set here even though empty array already sent to client.

What you want to do is wait for the callback to finish first.

There’s three ways you can do this with Meteor

1. Use Meteor.wrapAsync

// Wrap the function you want to call
// The second parameter sets `this` in the wrapped function. 
// I have no idea if the GoogleApi module you're using needs this, but I'm including it just in case
const wrappedGoogleApiGet = Meteor.wrapAsync(GoogleApi.get, GoogleApi);
// (...)
    'fetchMessages'() {
        const messages = [];
        const user = Meteor.users.findOne(Meteor.userId());
        const email = user.services.google.email;

        let url = '/gmail/v1/users/' + encodeURIComponent(email) + '/messages?q=is:unread';
        // Now you can call the wrapped function as though it was synchronous
        const data = wrappedGoogleApiGet(url);
        (...)
        messages.push(message);
        (...)
        return messages;
    }

Here’s the docs on Meteor.wrapAsync:

Long before Promises and async functions, Meteor provided sync style async calls on the server using Fibers. If you’re curious, you can get a rundown here: https://benjamn.github.io/goto2015-talk/#/

2. Return a Promise:

'fetchMessages'() {
    const messages = [];
    const user = Meteor.users.findOne(Meteor.userId());
    const email = user.services.google.email;

    let url = '/gmail/v1/users/' + encodeURIComponent(email) + '/messages?q=is:unread';
    // Return the promise, that will *eventually* resolve with the value you want to send
    return new Promise(function (resolve, reject) { 
        GoogleApi.get(url, function (error, data) {
            (...)
            messages.push(message);
            (...)
            // Resolve the promise with the value of messages
            resolve(messages);
        });
    });
}

This works because Meteor checks if you’re returning a promise from a method and will automatically await the result before sending it to the client

3. Use Async/Await

async functions and async/await work best in when the library you’re using already returns promises or if you can promisify the function in question.
I’ll use the pify module to promisify the function in the example

import pify from 'pify'
// promisify the function you want to call
const GoogleApiGetPromise = pify(GoogleApi.get);
(...)
// Change the function declaration to an async function
'fetchMessages': async function() {
    const messages = [];
    const user = Meteor.users.findOne(Meteor.userId());
    const email = user.services.google.email;

    let url = '/gmail/v1/users/' + encodeURIComponent(email) + '/messages?q=is:unread';
    // Now when we run the promisified function it returns a promise that we 
    // can wait for the value of with `await`
    const data = await GoogleApiGetPromise(url);
    (...)
    messages.push(message);
    (...)
    return messages;
}

Note that await is only available inside an async function. async functions always return a promise.

This one is most similar to the Meteor example, except that it uses pure javascript.
One notable difference is that when Meteor sees an async function it acts as though you ran this.unblock() inside the function, and so the order in which methods are called is not guarenteed (unlike with wrapAsync).


#14

Guessing by your project folder structure, maybe that’s because you put the controller file nested deeper than the array you’re trying to call. Since meteor handle the most deep nested first and the shallower next.


#15

Hey, coagmano,

I used to work with angular and had the opportunity to work with promises and such, but it has been quite a while.

I greatly appreciate your explanation and the options you’ve presented. That’s really helpful!

For the time being, it would seem to me, and taking into account your explanation, that Meteor.wrapAsync would be the best way to go.

I’ll most definitely have a go at it later today and share the feedback :slight_smile:

Thank you!


#16

Hey, all,

Just wanted to let you know that in the end, I went with coagmano’s second suggestion:

Still running into some issues with serving the attachments as pdf (they’re returned in the webservice as base64url), but I’ll try to isolate one of the attachments and work from there.

Thanks for all your suggestions.

Kind regards