Similar issues have been posted, but none quite match what I've run into. I'm doing a simple POST to an internal server to get back product data. The call is successful and I see the JSON data correctly logging to my terminal when I do a console.log on the server side. The issue arises on the client side, when in the callback, the result and error both are undefined.
Server:
Meteor.methods({
ProductSearch: function(searchTerm) {
var method = 'POST';
var url = 'server';
var options = {
headers:{"content-type":"application/json"},
data: {
query:"trees"
}
};
return HTTP.call(method, url, options, function (error, result) {
if (error) {
console.log("ERROR: ", result.statusCode, result.content);
} else {
var txt = JSON.parse(result.content);
console.log("SUCCESS: Found "+txt.totalResults+" products");
}
});
}
});
Client:
Meteor.call('ProductSearch', searchTerm, function (error, result) {
if (error) {
console.log("error occured on receiving data on server. ", error );
} else {
var respJson = JSON.parse(result.content);
Session.set("productSearchResults", respJson);
}
});
When I log the values of error, and result on callback, they are both undefined, and I get the following error: Exception in delivering result of invoking 'ProductSearch': TypeError: Cannot read property 'content' of undefined
In your server-side method, you're not correctly returning the result of the HTTP.call, since you're using the asynchronous version, HTTP.call will return undefined and the result will only be accessible in the callback.
Use the synchronous version of HTTP.call instead and you'll be fine.
try{
var result = HTTP.call(method, url, options);
return JSON.parse(result.content);
}
catch(exception){
console.log(exception);
}
See the corresponding docs for HTTP.call for additional information.
asyncCallback Function
Optional callback. If passed, the method runs
asynchronously, instead of synchronously, and calls asyncCallback. On
the client, this callback is required.
https://docs.meteor.com/#/full/http_call
Related
I have a Flutter app and I'm trying to get a client nonce from braintree. Per the braintree documentation, I have this in my cloud function:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
throw new functions.https.HttpsError('unknown', 'Error getting client nonce');
} else {
console.log(`token: ${response.clientToken}`);
return response.clientToken;
}
});
});
Then, in my Flutter app I call the function (again, I'm following what the plugin says):
try {
HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'getClientNonce',
);
dynamic result = await callable.call({});
final value = result.data;
debugPrint('token: $value');
var data = await BraintreePayment().showDropIn(
nonce: value,
amount: '2.0',
enableGooglePay: false,
inSandbox: true);
print("Response of the payment $data");
} on CloudFunctionsException catch (e) {
debugPrint('An error occurred');
} catch (e) {
debugPrint('An error occurred');
}
}
I tried changing the cloud function so that it only returns a random number (as soon as the function is executed), and my Flutter app is correctly receiving the value (so the cloud function is communicating fine). And in my Firebase console, I am able to view the client nonce specified by console.log. But the function is for whatever reason unable to return the actual client nonce. (It should be should be some string hash that is >2000 characters long)
The callable function needs to return a promise from the top-level of the function callback that resolves with the value to return. Right now, you're returning nothing from the top-level. The return you have now is just returning a value from the inner callback function that you pass to braintree API. This isn't going to propagate to the top level.
What you need to do is either use a version of braintree API that returns an API (if one exists), or promisify the existing call that uses a callback.
See also "3. Node style callback" here: How do I convert an existing callback API to promises?
I have not tested this, but the general format if you apply that pattern will look more like this:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
reject(new functions.https.HttpsError('unknown', 'Error getting client nonce'));
} else {
console.log(`token: ${response.clientToken}`);
resolve(response.clientToken);
}
});
});
});
I am hitting a number of API's from JQuery, and caching the result of each so that the data can be re-used multiple times in the page to render some dashboard widgets in different formats.
The problem is that if an API returns a 500 status code with error, I don't want to try and draw the widget, but capture the error in a friendly way.
However, I cannot figure out how .catch works with the JQuery.ajax() function. After reading here, here, here, here and a dozen others, I've got so far but always get the same console error:
TypeError: LoadDataFromApi(...).then(...).catch is not a function
I've tried to comment the code to explain what I'm trying to do at each stage. Please somebody explain why the whole .catch thing isn't working for me.
// Cache object to save API data for re-use
var requestCache = {};
// Get API data and save to cache
function LoadDataFromApi(apiUrl) {
if (!requestCache[apiUrl]) {
var result = $.ajax({
type: 'GET',
url: apiUrl,
dataType: "json",
statusCode: {
500: function (xhr) {
var err = JSON.parse(xhr.responseText);
console.log('Message:' + err.Message);
// throw err.Message; // removed because this was always an "uncaught exception", even if used within try/catch
},
200: function (xhr) {
// Do nothing here - put result into cache regardless of status code
}
}
});
requestCache[apiUrl] = result; // save the JSON data into cache
}
return requestCache[apiUrl];
}
// Called by page on load
function LoadJsonData() {
LoadDataFromApi('/api/GetFoo?Row=10')
.then(function (data) {
RenderChart(data, 'Removed for legibility');
})
.catch(function (error) {
console.log('Promise catch: ' + error);
});
LoadDataFromApi('/api/GetFoo?Row=10') // this returns cached data because API has already been hit
.then(function (data) {
RenderChart(data, 'Removed for legibility');
})
.catch(function (error) {
console.log('Promise catch: ' + error);
});
LoadDataFromApi('/api/GetBar')
.then(function (data) {
RenderChart(data, 'Removed for legibility');
})
.catch(function (error) {
console.log('Promise catch: ' + error);
});
}
Use .fail() as described in your first link here
Depending on your jQ version
"Deprecation Notice: The jqXHR.success(), jqXHR.error(), and
jqXHR.complete() callbacks are removed as of jQuery 3.0. You can use
jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead."
EDIT:
You error callback should accept 3 arguments, so make it so
function(jqXHR,textStatus,errorThrown ){}
JQuery does not return typical promise, it's$.Deferred in that case:
http://api.jquery.com/jquery.ajax/
http://api.jquery.com/category/deferred-object/
More on that here, with answers:
Deferred versus promise
I'm experiencing a strange problem with meteor. I'm trying to make HTTP call an use the data in a React-Component. But I can't access the returned data.
on the server:
'get': function get() {
try {
const data = Meteor.http.get('url', {
params: {
"api_key": "key",
"attribute": "attribute"
}
}
return data.data;
} catch (exception) {
throw new Meteor.Error('500', exception);
}
},
on the client: i've set up a container using withTracker() so that i can access the http response as props in my react component.
export default withTracker(() => {
var data = [];
Meteor.call('get', function(error, success) {
if (error) {
console.log('error', error.reason);
}
if (success) {
data.push(success);
console.log('success', success);
}
});
return {
data,
};
})(Component);
I've tried all possible combination. Using arrays and objects, but none of them worked out. When using console.log(data), I get some data on the client. But using console.log(data[0]) return undefined.
I've also tried returning an object from the server 'get' method. An using js Object.assign. But when calling console.log(data.name) for example, I get undefined on the client.
Maybe I'm not solving it the right way, but I don't understand why this is always returning undefined when I tried to access the object's data.
Solved by calling putting Meteor.call in a component method, and passing another component method in the callback because Meteor.call does not support promises or async / await. reference
I am using request-promise node module. I have been following the docs, and believe I should have set everything up correctly, however, I am getting the following error:
Unhandled rejection StatusCodeError: 400 - "{\n \"error\" : {\n
\"status\" : 400,\n \"message\" : \"invalid id\"\n }\n}"
at new StatusCodeError (/Users/fitz035/Desktop/sony/travelLand/node_modules/request-promise/node_modules/request-promise-core/lib/errors.js:32:15)
My code looks like the following.
var request = require('request');
var rp = require('request-promise');
rp('apiOne' + mood)
.then(function(error, response, body) {
console.log(body)
}
}).then(function(error) {
for (var i = 0; i < array; i++) {
rp('apiTwo', function(error, response, body) {
console.log(body))
});
}
}).then(function(error, response, body) {
rp('apiThree' + songListUrl, function(error, response, body) {
console.log(body))
});
})
.catch(function(err) {
console.log(err)
});
EDIT
Is this the correct approach?
var options = {
uri: 'https://jsonplaceholder.typicode.com/posts/1',
json: true
}
rp(options).then(function(data){
console.log(data)
var optionsTwo = {
uri: 'http://www.google.com',
}
rp(optionsTwo).then(function(data){
console.log(data)
console.log(answer);
})
})
You're calling rp as though it were request in a couple of places, such as in your for loop. Since you're not doing anything with the return value of those calls, you're not handling either resolution or rejection of those promises, thus getting the error warning you that there was a rejection you didn't handle.
The calls that you do use the return value from look incorrect as well. The callbacks you're passing in declare NodeJS-callback style argument lists, but that's not how promises work. Instead of a single callback that gets passed an error or null as the first argument, you register separate callbacks for resolution (success) and rejection (error).
I suggest studying the examples on the request-promise page in detail.
Re your follow-up:
Is this the correct approach?
var options = {
uri: 'https://jsonplaceholder.typicode.com/posts/1',
json: true
}
rp(options).then(function(data){
console.log(data)
var optionsTwo = {
uri: 'http://www.google.com',
}
rp(optionsTwo).then(function(data){
console.log(data)
console.log(answer);
})
})
No, that still doesn't handle rejections. If your goal is to do those two requests in series (not in parallel), then see comments:
var options = {
uri: 'https://jsonplaceholder.typicode.com/posts/1',
json: true
};
rp(options)
.then(function(data){
console.log(data)
var optionsTwo = {
uri: 'http://www.google.com'
};
return rp(optionsTwo).then(function(data){ // Note the return
console.log(data)
console.log(answer); // Don't know where `answer` is coming from...?
// Note that by not returning anything, you convert
// the resolution value to `undefined`. That's fine if
// you don't need it, but it's worth pointing out.
});
})
.catch(function(error) {
// One of the two operations above failed
});
I have a Meteor method that wraps around an http.get. I am trying to return the results from that http.get into the method's return so that I can use the results when I call the method.
I can't make it work though.
Here's my code:
(In shared folder)
Meteor.methods({
getWeather: function(zip) {
console.log('getting weather');
var credentials = {
client_id: "string",
client_secret: "otherstring"
}
var zipcode = zip;
var weatherUrl = "http://api.aerisapi.com/places/postalcodes/" + zipcode + "?client_id=" + credentials.client_id + "&client_secret=" + credentials.client_secret;
weather = Meteor.http.get(weatherUrl, function (error, result) {
if(error) {
console.log('http get FAILED!');
}
else {
console.log('http get SUCCES');
if (result.statusCode === 200) {
console.log('Status code = 200!');
console.log(result.content);
return result.content;
}
}
});
return weather;
}
});
For some reason, this does not return the results even though they exist and the http call works: console.log(result.content); does indeed log the results.
(Client folder)
Meteor.call('getWeather', somezipcode, function(error, results) {
if (error)
return alert(error.reason);
Session.set('weatherResults', results);
});
Of course here, the session variable ends up being empty.
(Note that this part of the code seems to be fine as it returned appropriately if I hard coded the return with some dummy string in the method.)
Help?
In your example Meteor.http.get is executed asynchronously.
See docs:
HTTP.call(method, url [, options] [, asyncCallback])
On the server, this function can be run either synchronously or
asynchronously. If the callback is omitted, it runs synchronously and
the results are returned once the request completes successfully. If
the request was not successful, an error is thrown
Switch to synchronous mode by removing asyncCallback:
try {
var result = HTTP.get( weatherUrl );
var weather = result.content;
} catch(e) {
console.log( "Cannot get weather data...", e );
}
Kuba Wyrobek is correct, but you can also still call HTTP.get asynchronously and use a future to stop the method returning until the get has responded:
var Future = Npm.require('fibers/future');
Meteor.methods({
getWeather: function(zip) {
console.log('getting weather');
var weather = new Future();
var credentials = {
client_id: "string",
client_secret: "otherstring"
}
var zipcode = zip;
var weatherUrl = "http://api.aerisapi.com/places/postalcodes/" + zipcode + "?client_id=" + credentials.client_id + "&client_secret=" + credentials.client_secret;
HTTP.get(weatherUrl, function (error, result) {
if(error) {
console.log('http get FAILED!');
weather.throw(error);
}
else {
console.log('http get SUCCES');
if (result.statusCode === 200) {
console.log('Status code = 200!');
console.log(result.content);
weather.return(result);
}
}
});
weather.wait();
}
});
There's not really much advantage to this method over a synchronous get in this case, but if you're ever doing something on the server which can benefit from something like an HTTP call running asynchronously (and thus not blocking the rest of the code in your method), but you still needs to wait for that call to return before the method can, then this is the right solution. One example would be where you need to execute multiple non-contingent gets, which would all have to wait for each other to return one by one if executed synchronously.
More here.
Sometimes asynchronous calls are preferable. You can use async/await syntax for that, and you need to promisify HTTP.get.
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
const httpGetAsync = (url, options) =>
new Promise((resolve, reject) => {
HTTP.get(url, options, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
Meteor.methods({
async 'test'({ url, options }) {
try {
const response = await httpGetAsync(url, options);
return response;
} catch (ex) {
throw new Meteor.Error('some-error', 'An error has happened');
}
},
});
Notice that meteor test method is marked as async. This allows using await operator inside it with method calls which return Promise. Code lines following await operators won't be executed until returned promise is resolved. In case the promise is rejected catch block will be executed.