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