Meteor async method call - meteor

I am very new to meteor and I have a method in Meteor.methods like :
sendStory(story) {
HTTP.call("GET", "https://offline-news-api.herokuapp.com/stories", function(error, response){
if(error){
console.log("error")
}else{
console.log(response)
var story = story
return story
}
})
}
and then I am calling this on my cliet like:
Meteor.call('sendStory', this.story, function(res){
console.log("some story")
console.log(res)
})
Here it is not printing the res value it is giving undefined and the api call is made at last..
How can I make api call first and then go to callback from api
Thank you ..

Well,
dont use the callback for http call like this:
sendStory(story) {
var story = HTTP.call("GET", "https://offline-news-api.herokuapp.com/stories");
return story;
}
refer here to Meteor Docs
You cannot return from a callback since Meteor methods run within fibers.

You could use a Future:
sendStory(story) {
Future = Npm.require('fibers/future');
var apiFuture = new Future();
HTTP.call("GET", "https://offline-news-api.herokuapp.com/stories", function(error, response){
if(error){
console.error("Error: ", error);
apiFuture.throw(error);
}else{
console.log("Response: ", response);
apiFuture.return(response);
}
});
return apiFuture.wait();
}
and on the client:
Meteor.call('sendStory', this.story, function(err, res){
console.log("some story");
if (err) {
console.error(err);
} else {
console.log("Great! A response from the API: ", res);
}
});

Like guns mentioned, you are returning undefined from server method. The method doesn't wait for the asynchronous function to terminate. Instead it returns.
In the server, I always go with synchronous approach.

Related

Jasmine 4: Async function did not complete within 5000ms issue

I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});

Parse Cloud Code response is null after calling a function with callback

I have a cloud code from which I call an external function.
The cloud code response is null but the console displays the response
my cloud code ;
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
externalFunction (ccaddrequest, function (err, result) {
console.log(result);
return result;
}) ;
} catch (e) {
console.log("Error");
}
});
console.log (result); shows the values from the external function, but the return result; returns null
how can I get the external function response as response of my cloud code function ?
The problem is that your externalFunction uses a callback to return its result. That is an asynchronous event, meaning that it happens after your cloud functions has been processed.
The cloud function will execute var ccaddrequest... and then call externalFunction but it won't "wait" for externalFunction to call the callback function if it contains asynchronous commands.
So you need to wrap the externalFunction in a Promise (see how to promisify callbacks) and then await the result of it.
Plus you need to return the result of the Promise, so in your code you need to add
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
var result = await externalFunctionPromise(...);
return result;
} catch (e) {
console.log("Error");
}
});

Asynchronous https firebase functions

Should HTTPS functions return asynchronous promises like realtime functions have to?
We haven't been returning in HTTPS functions (just using res.status.send etc), and it looks like firebase/function-samples aren't either. But the documentation is slightly ambiguous https://firebase.google.com/docs/functions/terminate-functions .
This works now in the latest Firebase:
exports.asyncFunction = functions.https.onRequest(async (request, response) => {
const result = await someAsyncFunction();
response.send(result);
});
HTTP functions currently do not respect returned promises - they require a sent result in order to terminate normally. If an HTTP function doesn't send a result, it will time out.
All other types of functions require a returned promise in order to wait for asynchronous work to fully complete.
If you don't have any async work to wait for, you can just return immediately.
These are the three cases outlined in the docs.
After much looking around , this is implementation with a Promise worked for me to return a value from a Google Cloud Function where the function needs to make a third-party asynchronous call :
exports.getSomeAccessToken = functions.https.onCall((data, context) => {
var dataStr = JSON.stringify(data, null, '\t');
console.log('ENTER [getSomeAccessToken], got dataStr: ' + dataStr);
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, gatewayResponse) {
var result = {
clientToken: gatewayResponse.clientToken
};
var resultStr = JSON.stringify(result, null, '\t');
console.log("resultStr : " + resultStr);
resolve(result);
});
});
});
Your cloud functions should return"end" with either of the following
res.redirect(), res.send(), or res.end()
What they mean by returning promises, is lets imagine you have a cloud function that updated a node in your realtime database, you would like to complete that work before responding to the HTTP request.
Example code
let RemoveSomething = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Remove something
DoDatabaseWork()
.then(function (result) {
res.status(200).send();
})
.catch(function (err) {
console.error(err);
res.status(501).send();
});
});
});
Update: Added DoDatabaseWork example.
const DoDatabaseWork = function () {
return new Promise(function (resolve, reject) {
// Remove SomeNode
admin.database().ref('/someNode/').remove()
.then(function (result) {
resolve();
})
.catch(function (err) {
console.error(err);
reject();
});
});
}

Meteor async function in method

I read everything concerning this issue and I must admit I am still pretty lost.
I have a payment system which need to do HTTP POST queries to validate payment.
So basically here is my code on the server:
Payment.sendPayment = function(callback){
HTTP.post(..., function(err, result){
if(err) throw new Error('This is an error!');
callback && callback(null);
});
}
With the method as follow:
Meteor.methods({
buy: function(){
Payment.sendPayment(function(err){
if(err) throw new Meteor.Error('buy', err);
});
}
});
It does not work since the return is not in the main function. I tried with wrapAsync:
Meteor.methods({
buy: function(){
var sendPayment = Meteor.wrapAsync(Payment.sendPayment);
console.log(sendPayment());
}
});
Still does not work. I couldn't find any simple example of wrapAsync. I found some stuff concerning Future package but the posts were quite old.
Any idea to do this?
Thank you!
If you want to use futures, here is an example:
var Future = Npm.require('fibers/future');
Meteor.methods({
buy: function(){
var future = Future();
Payment.sendPayment(function(err){
if(err) {
return future.return(err); //return the error
}
return future.return(); //you can return the result here if you want
});
return future.wait();
}
});

Returning data from server method to client after server calls an HTTP API

I am trying to learn Meteor, starting by writing a simple application where the server calls an HTTP API based on user input, processes the information, then returns it to the client to display it.
I am not having much success. I can't seem to return the result from server to client:
if (Meteor.isServer) {
Meteor.methods({
checkTransit: function(method, url, options) {
this.unblock();
return Meteor.http.call(method, url, function(error, result) {
if (error) {
console.log('SERVER ERRR');
console.log(error);
} else {
console.log('SERVER RESULT');
console.log(result);
}
});
}
})
}
if (Meteor.isClient) {
Template.body.events({
"submit .new-task": function(event) {
// Prevent default browser form submit
event.preventDefault();
var text = event.target.text.value;
var method = 'GET';
var url = 'http://sometransitapi.com';
var options = {
headers: {
'accept': 'application/XML',
'content-type': 'application/XML'
}
}
Meteor.call('checkTransit', method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
var parser;
parser = new DOMParser();
var xmlDoc
xmlDoc = parser.parseFromString(result, "text/xml");
console.log(xmlDoc);
}
})
}
})
}
I can see the results being returned to the result variable at isServer, but I can not pass the result to the xmlDoc variable in isClient. What am I doing wrong? Is this the correct way to structure things for what I want to do in Meteor?
Meteor.http.call is being called asynchronously in your server code (you are passing a callback). The functions in your Meteor.methods object expect to return a value, so you should be calling Meteor.http.call synchronously. Changing your server code to below should do the trick.
if (Meteor.isServer) {
Meteor.methods({
checkTransit: function(method, url, options) {
this.unblock();
// This will throw an exception and return it as the error object
// in your Meteor.call if an error occurs, otherwise it will
// return an empty error object and the result object will be
// the return value from Meteor.http.call
return Meteor.http.call(method, url);
}
})
}
If you want to keep the logic on the server side then #Curtis' answer should help you.
Looking at what you have, I don't see much of a point of getting the server to do the work. The Http api is available everywhere in Meteor. You are just fetching data, which can be solely done on the front end. This would also technically make it faster. Taking your code and moving it around a bit you get the following.
if (Meteor.isClient) {
Template.body.events({
"submit .new-task": function(event) {
// Prevent default browser form submit
event.preventDefault();
var text = event.target.text.value;
var method = 'GET';
var url = 'http://sometransitapi.com';
var options = {
headers: {
'accept': 'application/XML',
'content-type': 'application/XML'
}
}
Http.call(method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
var parser;
parser = new DOMParser();
var xmlDoc
xmlDoc = parser.parseFromString(result, "text/xml");
console.log(xmlDoc);
}
});
}
});
}

Resources