How to use Node.js express async callback response with a Service - javascript

I'm new to node and trying to understand how to use async call inside an other async function, here's an example.
My routing:
/**
* PAGE: PROFILE EXPOSITIONS
*/
exports.expositions = async(function* (req, res) {
respond(res, 'users/profile/exhibitors', ProfileResponse.expositions( req ));
});
Here's what ProfileResponse.expositions look like
exports.expositions = function*(req) {
return {
title: "Expositions",
user: req.user,
path: "/profile",
waiting_exposition_requests: yield UserService.getWaitingExpositionRequest(req.user.id)
};
};
I'm trying to use getWaitingExpositionRequest which looks like this
exports.getWaitingExpositionRequest = function*(userId) {
let currentUser = User.find(userId);
let waiting_exposition_requests = [];
currentUser.exposition_requests.forEach(function(exposition_request) {
if(exposition_request.status === "waiting_for_exhibitor") {
waiting_exposition_requests.push(exposition_request);
}
});
return waiting_exposition_requests;
};
But in the console I get a Promise error, any idea how I can use this async function returned? I'm sure it's a syntax error but I'm a little lost with the async stuff.

Related

Why is my code error not stopping the function in nodejs?

My self and another developer are working on an API using node.js and we are not advanced coders yet. We ran into problem. I will try and explain the problem here with sample references.
We have an API route that would be called on the frontend, please this is just a sample and not the real code. client said the codes should be private. We want to stop the function when an error is detected in another function we called. I am sure that we are not doing it rightly. Here is the API route for this post request and we called another function that we exported and imported here.
We simply want the httpCreateHarsh function to end if there is an error in the anotherFunction. With what we have, the error is seen in console.log when the user is not an admin for example but the httpCreateHarshfunction keeps running until it reaches the last line. Is this possible? Or is there another way we can structure the code to achieve this?
Shared sample of my code
const callAnotherFunction = require("../anotherfunction")
const httpCreateHarsh = async(req, res) => {
await callAnotherFunction(req, res);
return res.status(200).json('created')
}
//This is the second function we called:
const obj = {
status: 'success',
code: '244'
}
const anotherFunction = async(req, res) => {
if (req.body.user !== 'admin') {
return res.status(401).json('Unauthorized')
}
return obj
}
export default = anotherFunction
//The route here:
const express = require('express');
const router = express.Router();
const httpCreateHarsh = require('../httpCreateHarsh');
router.post("/harsh", httpCreateHarsh)
You couldn't return the res object in your second function.
To solve this problem you could throw exceptions and catch them in your handler function.
// request handler function
const httpCreateHarsh = async (req, res) => {
try {
await callAnotherFunction(req, res);
} catch (e) {
return res.status(401).json('Unauthorized')
}
return res.status(200).json('created')
}
const anotherFunction = asyn(req, res) => {
if (req.body.user !== 'admin') {
throw new Error('Unauthorized')
}
return obj
}
What you can do is you can wrap the code in httpCreateHarsh in an try...catch so whenever there is error inside it it will trigger the catch block and u exit the api.
const httpCreateHarsh = async(req, res)=>{
try{
await callAnotherFunction(req, res);
return res.status(200).json('created')
} catch(err){
return res.status(401).json('Unauthorized')
}
}
As an addition to this code you can return a promise from anotherFunction so that the catch block will be triggered once the promise is rejected.
For Exmaple:
const anotherFunction = async(req, res) => {
return new Promise(function(myResolve, myReject) {
if (req.body.user !== 'admin') {
myReject();
}
myResolve(obj);
});
}
If the code runs as you want it, it will generate the "cannot set headers after they are sent to the client" error, because you will be returning 2 responses
the first will be "unauthorized" by "anotherFunction" function and then the other response which is "created" of the current function "httpCreateHarsh".
what you should do instead is to call the "anotherFunction" as a middleware before moving to the "httpCreateHarsh" function.
it can be done this way:
// anotherfunction.js file containing the function you want to import
module.exports = {
async anotherFunction(req, res) {
if (req.body.user !== 'admin') {
return res.status(401).json('Unauthorized')
}
// this way, you can access this object from the "httpCreateHarsh" function by using req.body.obj
req.body.obj = {
status: 'success',
code: '244'
}
// this next indicates that there were no errors, and the next function will be called
next();
}
}
const httpCreateHarsh = async(req, res) => {
// do wathever you want here
return res.status(200).json('created')
}
//The route here:
const express = require('express');
const router = express.Router();
const httpCreateHarsh = require('../httpCreateHarsh');
const callAnotherFunction = require("../anotherfunction")
router.post("/harsh", (req, res, next) => callAnotherFunction(req, res, next), httpCreateHarsh)

How to mock a method inside Express router Jest test?

I'm trying to test a router in Node.js app with Jest + Supertest, but my router is making a call to service, which is calling the endpoint:
router.post('/login', async (req, res, next) => {
try {
const { username, password } = req.body;
// I WANT TO MOCK userService.getUserInfo FUNCTION, BECAUSE IT IS MAKING A POST CALL
const identity = await userService.getUserInfo(username, password);
if (!identity.authenticated) {
return res.json({});
}
const requiredTenantId = process.env.TENANT_ID;
const tenant = identity.tenants.find(it => it.id === requiredTenantId);
if (requiredTenantId && !tenant) {
return res.json({});
}
const userResponse = {
...identity,
token: jwt.sign(identity, envVars.getVar(envVars.variables.AUTH_TOKEN_SECRET), {
expiresIn: '2h',
}),
};
return res.json(userResponse);
} catch (err) {
return next(err);
}
});
This is my test that works well:
test('Authorized - respond with user object', async () => {
const response = await request(app)
.post('/api/user/login')
.send(users.authorized);
expect(response.body).toHaveProperty('authenticated', true);
});
this is how getUserInfo function looks like:
const getUserInfo = async (username, password) => {
const identity = await axios.post('/user', {username, password});
return identity;
}
but it executes the method getUserInfo inside a router and this method is making a REST call - I want to mock this method in order to avoid REST calls to other services.
How it could be done?
I've found a mockImplementation function in Jest docs https://jestjs.io/docs/en/mock-function-api.html#mockfnmockimplementationfn
but how I can mock func inside a supertest testing?
You can use jest's auto mocking at the top of your test
like so:
jest.mock('./path/to/userService');
// and include it as well in your test
const userService = require('./path/to/userService');
it will generate a mock of the entire module and every function will be replaced with jest.fn() with no implementation
and then depending on the userService if it's just an object it's getUserInfo method will be a jest.fn() and you can set it's return value like this:
// resolved value as it should return a promise
userService.getUserInfo.mockResolvedValue(mockIdentity);
and the mockIdentity will have to look something like this:
const mockIdentity = {
authenticated: true,
tenants: [
{
id: "x12",
mockInfo: "mock-info-value"
}
],
mother: "Superwoman",
father: "Superman"
})
}

Getting a 404 error when using the call log API

I'm using Node and trying to do a simple API call to the call log. My code came from the docs, but modified a bit. Login works just fine so my credentials are ok.
const fetch = require('node-fetch');
const SDK = require('#ringcentral/sdk').SDK;
RINGCENTRAL_CLIENTID = '<SECRET>';
RINGCENTRAL_CLIENTSECRET = '<SECRET>';
RINGCENTRAL_SERVER = 'https://platform.devtest.ringcentral.com';
RINGCENTRAL_USERNAME = '<SECRET>';
RINGCENTRAL_PASSWORD = '<SECRET>';
RINGCENTRAL_EXTENSION = '101';
var rcsdk = new SDK({
server: RINGCENTRAL_SERVER,
clientId: RINGCENTRAL_CLIENTID,
clientSecret: RINGCENTRAL_CLIENTSECRET
});
const platform = rcsdk.platform();
exports.handler = async function(event, context) {
await rcLogin();
let callLog = await rcCallLog();
console.log(callLog);
return {
statusCode: 200,
body: JSON.stringify(callLog)
}
}
async function rcLogin() {
return platform.login({
username: RINGCENTRAL_USERNAME,
password: RINGCENTRAL_PASSWORD,
extension: RINGCENTRAL_EXTENSION
});
}
async function rcCallLog() {
return platform.get('/account/~/extension/~/call-log', {
view: 'Detailed'
})
}
It logs in just fine, but then the callLog call returns errorMessage: '404 Not Found'
So I figured it out. Changing the call to this worked:
return platform.get('/restapi/v1.0/account/~/extension/~/call-log', {
It may just be a bug in the docs for reading the call log (https://developers.ringcentral.com/guide/voice/call-log/reading-call-log).

How to combine responses from two calls in one response?

I'm getting an empty response ({}), whereas my expected response is of format:
{
locationResponse: "location foo",
forecastResponse: "forecast bar"
}
In my index file I have:
const {getCity} = require('./routes/city');
const {getForecasts} = require('./routes/forecast');
app.get('/forecasts', function (req, res) {
var location = getCity(req, res);
var forecast = getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});
Inside forecast file I have the following, and a similar one is in city file:
module.exports = {
getForecasts: (req, res) => {
var result = //mySQL DB calls and processing
console.log("Inside getForecasts");
console.log(result); //actual result printed
return "Forecast";
}
UPDATE: So I added some logs right before each call's return statements and figured out that the logs are printed in the following order, which means, it is not working as expected because I have not considered the fact that they are asynchronous calls.
Inside index.js
undefined
undefined
Inside getForecasts
{result}
The problem here is that in your ./routes/forecast/ getForecasts method, you're telling the response to send, with the data "Forecast". You should only ever use res.send once per request, as this will resolve the response and return to the client.
Instead, your getForecasts method should just return whatever data you need, and your index file should handle the response. If you need getForecasts to handle a response too, perhaps because you're sending requests directly to a forecasts endpoint that doesn't require location data, then you can refactor your code so that both index and forecasts make a call to get the data you need. For example:
/* index.js */
const {getCity} = require('./data/city');
const {getForecasts} = require('./data/forecast');
app.get('/forecasts', function (req, res) {
var location = getCity();
var forecast = getForecasts();
res.send({locationResponse: location, forecastResponse: forecast});
});
/* data/forecast.js */
module.exports = {
getForecasts: () => {
return "Forecast";
}
};
/* data/city.js */
module.exports = {
getCity: () => {
return "City";
}
};
Then you can also have:
/* routes/forecast.js */
const {getForecasts} = require('../data/forecast');
module.exports = {
getForecasts: (req, res) => {
res.send(getForecasts());
}
};
The above may be overcomplicating things, but I made the assumption that if you're using a routes directory, you probably want route handlers to be stored there. Hope this helps.
Seems both of your getCity() and getForecasts() functions are async. These asynchronous functions return a promise rather actual response.
So you can use simple asysn/await or Promise.all in JS to solve the issue.
Option 1: Use await for the promise to resolve before logging the message to the console:
app.get('/forecasts', async function (req, res) {
var location = await getCity(req, res);
var forecast = await getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});
Option 2: Use Promise.all() to wait for all the promises to have fulfilled.
app.get('/forecasts', function (req, res) {
var list = await Promise.all([getCity(req, res), getForecasts(req, res)]);
//these are logged as undefined
console.log("Inside index.js");
console.log(list[0]);
console.log(list[1]);
res.send({locationResponse: list[0], forecastResponse: list[1]});
});
You can make use of async/await syntax.
app.get('/forecasts', async function (req, res) {
var location = await getCity(req, res);
var forecast = await getForecasts(req, res);
//these are logged as undefined
console.log("Inside index.js");
console.log(location);
console.log(forecast);
res.send({locationResponse: location, forecastResponse: forecast});
});

Node.js: Awaiting a Require

I'm new the Node.js and I've been working with a sample project by a third party provider and I'm trying to use Azure Key Vault to store configuration values.
I'm having trouble getting a process to wait before executing the rest. I'll try to detail as much as I know.
The sample project has a file named agent.js which is the start page/file. On line 16 (agent_config = require('./config/config.js')[process.env.LP_ACCOUNT][process.env.LP_USER]) it calls a config file with values. I'm trying to set these value using Key Vault. I've tried many combinations of calling functions, and even implementing async / await but the value for agent_config always contains a [Promise] object and not the data returned by Key Vault.
If I'm right, this is because the Key Vault itself uses async / await too and the config file returns before the Key Vault values are returned.
How can Key Vault be added/implemented in a situation like this?
Here's what I've tried:
First updated agent.js to
let agent_config = {};
try {
agent_config = require('./config/config.js')['123']['accountName'];
} catch (ex) {
log.warn(`[agent.js] Error loading config: ${ex}`)
}
console.log(agent_config);
Test 1
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
Results
{ accountsId: undefined }
Test 2
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
async function config() {
module.exports = {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
config();
Results
{}
Test 3
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
config();
Results
{ accountId: { <pending> } }
Other
I've tried many others ways like module.exports = async (value) =< {...} (found through other questions/solutions without success.
I'm starting to think I need to do some "waiting" on agent.js but I haven't found good info on this.
Any help would be great!
One issue is that your getValue function is not returning anything as your returns need to be explicit.
(and without the promise being returned, there's nothing to await on)
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
You could also get away with less explicit returns using arrow functions..
const getValue = async (secretName, secretVersion) =>
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then(credentials => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion)
.then(response => response.Value);
});
Introducing the Azure Key Vault read, which is async, means your whole config read is async. There' nothing you can do to get around that. This will mean that the code that uses the config will need to handle it appropriately. You start by exporting an async function that will return the config..
async function getConfig() {
return {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
module.exports = getConfig;
In your agent code you call that function. This will mean that your agent code will need to be wrapped in a function too, so maybe something like this..
const Bot = require('./bot/bot.js');
const getConfig = require('./config/config.js');
getConfig().then(agentConfig => {
const agent = new Bot(agentConfig);
agent.on(Bot.const.CONNECTED, data => {
log.info(`[agent.js] CONNECTED ${JSON.stringify(data)}`);
});
});
The package azure-keyvault has been deprecated in favor of the new packages to deal with Keyvault keys, secrets and certificates separately. For your scenario, you can use the new #azure/keyvault-secrets package to talk to Key Vault and the new #azure/identity package to create the credential.
const { SecretClient } = require("#azure/keyvault-secrets");
const { DefaultAzureCredential } = require("#azure/identity");
async function getValue(secretName, secretVersion) {
const credential = new DefaultAzureCredential();
const client = new SecretClient(KEY_VAULT_URI, credential);
const secret = await client.getSecret(secretName);
return secret.value;
}
The DefaultAzureCredential assumes that you have set the below env variables
AZURE_TENANT_ID: The tenant ID in Azure Active Directory
AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant
AZURE_CLIENT_SECRET: The client secret for the registered application
To try other credentials, see the readme for #azure/identity
If you are moving from the older azure-keyvault package, checkout the migration guide to understand the major changes

Categories

Resources