Recursive Promise Call- Memory Scope Variable Issue - javascript

I have these functions for the purpose of retrieving a token through a api call out. If the user enters the wrong password, the promise will reject and on reject the function is called again to give the user another try.
If the user enters the right password the first time, there is no issue.
But if the user enters a wrong password and tries again...but tries again successfully, I am having a memory issue. Because of the recursive call to callApiToken() on the second try the promise is fullfilled and callApiToken().then(function() { refreshToken(); }) is called. file.token = JSON.parse(tokenString); is completed but in a different memory scope. Not sure what to do about this. I say this because the routine runs successfully. But the global var file is not populated as it should be.
createTokenFile() is called first.
var file = {};
function createTokenFile() {
block = true;
callApiToken()
.then(function() { refreshToken(); }) // ON THE SECOND RECURSIVE
.catch(function() { // RUN refreshToken() IS CALLED
callApiToken();
}).finally(function() {
block = false;
});
}
function refreshToken() {
var tokenFileAbsolute = path.join(__dirname, 'token-file.json');
return fs.readFileAsync(tokenFileAbsolute, {encoding: 'utf-8'})
.then(function(tokenString) {
file.token = JSON.parse(tokenString);
}).catch(function(err) {
console.log("No token-file.json file found. " .red +
"Please complete for a new one." .red);
createTokenFile();
});
}
UPDATE with other promise code that gives resolve for callApiToken()which is actually getCredentials:
Note: fs.writeFileAsync(tokenFile, token) does complete successfully on the second recursive call.
function getPassword(user) {
return readAsync({prompt: "Password: ", silent: true, replace: "*" })
.then(function(pass) {
return postAsync(URL, payload(user[0], pass[0]));
});
}
function getCredentials() {
return readAsync({prompt: "Username: "}).then(getPassword);
}
function writeToFile(data, response) {
tokenFile = path.join(__dirname, 'token-file.json');
token = JSON.stringify({
id: data.access.token.id,
expires: data.access.token.expires
});
return fs.writeFileAsync(tokenFile, token).then(function(err) {
if (err) throw err;
console.log("Token was successfully retrieved and written to " .cyan +
tokenFile .cyan + "." .cyan);
});
}

There is no such thing as a "memory scope". You simply have a timing issue!
If an action is asynchronous, you always have to return a promise from the function when you want to wait for the result - and you seem to do.
var file = {};
function createTokenFile() {
block = true;
callApiToken()
.then(function() {
return refreshToken();
// ^^^^^^ here
})
.catch(function() {
return callApiToken();
// ^^^^^^ and here
}).finally(function() {
block = false;
});
}
function refreshToken() {
var tokenFileAbsolute = path.join(__dirname, 'token-file.json');
return fs.readFileAsync(tokenFileAbsolute, {encoding: 'utf-8'})
.then(function(tokenString) {
file.token = JSON.parse(tokenString);
}).catch(function(err) {
console.log("No token-file.json file found. " .red +
"Please complete for a new one." .red);
return createTokenFile();
// ^^^^^^ and here!!!
});
}
Btw, my guess is that your recursion is flawed. Don't you want refreshToken to reject, and createTokenFile to call itself from within itself (instead of the second callApiToken())?

Related

Not able to catch exception from promise, error response is undefined

I've got some basic Javascript code that calls a stock API with symbols where the symbols are provided from a simple HTTP call like this:
GET http://localhost:4000/batch_stock_prices/?stocks=12312.
I believe I am misunderstanding the syntax for how to catch an exception from a promise..
An exception gets thrown that 12312 is an invalid symbol which I expect, on the terminal running the node server I see the exception but I'm not able to pass it back in the HTTP response. The error that's passed back in the response is 'undefined'. How can I catch the exception? Do I need a try catch somewhere?
const fetch = require('node-fetch')
const { IEXCloudClient } = require("node-iex-cloud");
const { type } = require('tap');
const iex = new IEXCloudClient(fetch, {
sandbox: true,
publishable: "pk_2f78524e5........23c327e24b5",
version: "stable"
});
'use strict'
async function getCurrentPriceOfBatchStocks(_stock) {
stocks_to_submit = _stock['stocks'];
console.log(stocks_to_submit)
response = await iex
.batchSymbols(stocks_to_submit)
.price()
.catch(function (error) { // <-- doesn't seem to get called
console.log("Exception: " + error);
throw error;
})
return new Promise((resolve, reject) => {
if (response) {
resolve(response)
} else {
reject(response); // <-- response is undefined. why?
}
});
}
const batchStocksSchema = {
querystring: {
type: 'object',
properties: {
stocks: {
type: 'string'
}
},
required: ['stocks']
}
}
module.exports = async function (fastify, opts) {
fastify.get('/batch_stock_prices/', {
schema: batchStocksSchema
}, async function (request, reply) {
try {
var response = await getCurrentPriceOfBatchStocks(request.query)
// console.log(response)
return reply
.code(200)
.send(response);
} catch (e) {
console.log(e)
return reply
.code(400)
.send('Bad Request, exception: ' + e) // outputs: ...exception: undefined
}
})
}
It's hard to say for sure what's wrong without running the code, but there are several issues with the use of async, await, and promises in the code, and with creating implicit globals. (Also various missing ;.) If we sort those out, it may be that whatever error is occurring will stop being obscured. See *** comments:
"use strict"; // *** This has to be at the very beginning of the compilation
// unit, it can't be later in the code as it is in the question
const fetch = require('node-fetch')
const { IEXCloudClient } = require("node-iex-cloud");
const { type } = require('tap');
const iex = new IEXCloudClient(fetch, {
sandbox: true,
publishable: "pk_2f78524e5........23c327e24b5",
version: "stable"
});
async function getCurrentPriceOfBatchStocks(_stock) {
// *** Declare `stocks_to_submit`
const stocks_to_submit = _stock['stocks'];
// *** Declare `response`
const response = await iex.batchSymbols(stocks_to_submit).price();
// *** Don't catch the error, let it propagate; the caller should
// know whether the call succeeded or failed
// *** Don't use `new Promise`, there's no purpose to it
return response;
}
const batchStocksSchema = {
querystring: {
type: 'object',
properties: {
stocks: {
type: 'string'
}
},
required: ['stocks']
}
};
// *** This function never uses `await`, so don't make it `async`
module.exports = function (fastify, opts) {
fastify.get('/batch_stock_prices/', {
schema: batchStocksSchema
}, function (request, reply) { // *** Typically old-style callback APIs don't do
// anything with the promise an `async` function
// returns, so don't pass `async` functions into them
getCurrentPriceOfBatchStocks(request.query)
.then(response => {
// *** No `return` here, we aren't resolving the promise from `then` with the result
// of `send`
reply
.code(200)
.send(response);
})
.catch(e => {
console.log(e);
// *** No `return` here, we aren't resolving the promise from `catch` with the
// result of `send`
reply
.code(400)
.send('Bad Request, exception: ' + e);
});
});
};
For why the catch is not called in this part:
response = await iex
.batchSymbols(stocks_to_submit)
.price()
.catch(function (error) { // <-- doesn't seem to get called
console.log("Exception: " + error);
throw error;
})
and why response is undefined:
return new Promise((resolve, reject) => {
if (response) {
resolve(response)
} else {
reject(response); // <-- response is undefined. why?
} });
This is the cause:
The promise returned by price() call had resolves an undefined value (instead of rejecting with an error). Your "await" wait for this undefined value and assigned it to "response" variable.
The price() when having problem have already handled the error and then print the details in the console:
Error: <html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx</center>
</body>
</html>
at IEXRequest.<anonymous> (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:128:35)
at step (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:32:23)
at Object.next (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:13:53)
at fulfilled (/home/runner/ArtisticAridSite/node_modules/node-iex-cloud/lib/request.js:4:58)
It wasn't really passing the error back in the chain to your code.
So on your question of "How can I catch the exception?". Unfortunately you probably has no way to receive the exception details (unless you can control the error handling behaviour of iex). You may consider to check whether the result is undefined and handle accordingly.

Function returned undefined, expected Promise or value async method

I have an async method named generateSession. And I want the promises system to wait till the call is done. I wait till the data is there and than delete the row at the database. For now, that doesn't make any sense.
But I get an error at this state. Function returned undefined, expected Promise or value It looks like this comes from calling the generateSession. But I don't know how to fix it.
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
});
});
var generateSession = function(){
// *** Return a promise
return new Promise(function(resolve, reject) {
opentok.createSession({mediaMode:"relayed"}, function(err, session){
if (err) {
reject(err);
} else {
resolve(session);
}
});
});
};
The onCreate of Firebase Functions itself needs to return a Promise:
exports.pending = functions.database
.ref('/groups/{groupId}/status/pending/{deviceId}/')
.onCreate(event => {
// You should return the result of generateSession() here
return generateSession().then(function(data) {
console.log("generated session:" + data.sessionId);
return event.data.ref.set({})
}).catch(function(err) {
console.log("error:", err);
// You probably don't want to catch here, let the error
// go through so that Cloud Functions can pick it up
})
});

What to return in a Firebase Cloud Function that has two or more methods that return a promise?

I have a Cloud Function that listens to a change in Firebase Database and responds with a function that returns a promise.
exports.clean_up_anonymous_users =
functions.database.ref('path/to/ref')
.onWrite(event => {
// Make Sure it's not a delete event
if (!event.data.val()) {
return 0;
}
const cleanUp = event.data.val();
// Normal Boolean check of my value
if (cleanUp === true) {
// Deletes Anonymous Users (returns a promise)
listAllUsers();
// Removes the value from the Database (returns a promise)
return removeFromQueue("path/to/ref");
}
// If cleanUp === false
return 0;
});
// ===============================================
// I think that's enough but if you wish, read on!
// ===============================================
function listAllUsers(nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
var userInfo = userRecord.providerData;
if (userInfo.length === 0) {
var uid = userRecord.uid;
admin.auth().deleteUser(uid)
.then(function() {
console.log("Successfully deleted user:", uid);
})
.catch(function(error) {
console.log("Error deleting user:", error);
});
}
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
})
.catch(function(error) {
console.log("Error listing users:", error);
});
}
function removeFromQueue(path) {
admin.database().ref(path).remove()
.then(function() {
console.log("Remove succeeded.")
})
.catch(function(error) {
console.log("Remove failed: " + error.message)
});
}
This code works as expected but shows an error in the Log window:
Error: Function returned undefined, expected Promise or value
How to get rid of that Error?
Your two function defined in the end do indeed return undefined as they are not returning anything. This would work in Ruby where last interpreted line in function body is returned.
So add return before the start of bodies inside those functions as inside them you are working with Promise chain that would return Promise as their execution will finish.
So in your code there will be this:
return admin.auth()...
And
return admin.database()...

Why can I get a Bluebird catch in the middle of chain stop the rest of the chain execution

I am building this promise chain. The goal is to have the first action check for uniqueness on a field in the DB, and then if unique, save the object. But if the object is not unique, it should not save, and should return an error response.
function(request, reply) {
var payload = request.payload;
checkThatEmailDoesNotExist().then(saveUser)
function checkThatEmailDoesNotExist() {
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return null;
})
.catch(function(err) { // ~This catch should stop the promise chain~
reply(err);
})
}
function saveUser() {
// ~But instead it is continuing on to this step~
return User.massAssign(request.payload).saveAsync()
.spread(function(user, numAffected) {
return reply(user);
})
.catch(function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
});
}
}
If an error is thrown in the checkThatEmailDoesNotExist() it's catch() should return the error, and stop processing the rest of the original promise chain.
Instead of acting that way, the catch() fires, and then continues to move on to the saveUser() function.
You are mixing promises and callbacks which is a horrible anti-pattern. The caller will simply use
the returned promise, there is no need to manually wire things back to callbacks.
function save(request) {
var payload = request.payload;
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return User.massAssign(request.payload).saveAsync()
})
.get(0)
/* equivalent to
.spread(function(user, numAffected) {
return user;
}) */
.catch(Promise.OperationalError, function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
});
}
Usage:
save(request).then(function(user) {
response.render(...)
}).catch(function(e) {
response.error(...)
})
If you wanted to expose a callback api, the sane way to do that is to bolt on a nodeify at the end of an existing promise api and call it a day:
function save(request, callback) {
var payload = request.payload;
return User.where({email: payload.email}).countAsync()
.then(function(count) {
if (count > 0) {
throw Boom.badRequest('The email provided for this user already exists')
}
return User.massAssign(request.payload).saveAsync()
})
.get(0)
/* equivalent to
.spread(function(user, numAffected) {
return user;
}) */
.catch(Promise.OperationalError, function(err) {
server.log(['error', 'api', 'auth'], err);
throw Boom.badRequest('Object could not be saved to database');
})
.nodeify(callback);
}
save(request, function(err, user) {
if (err) return response.error(...);
response.render(...);
});

Return Meteor.http results in method

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.

Categories

Resources