NodeJS insert voucher code to first person who calls API - node.js

I don't know if this has a solution already but I can't find it or I don't know what to search.
I have a rest api which returns a list of products and I want to add a voucher code to the response of the first person who calls the api. I'm using redis to cache the information of the user who received the code, that expires within 15 mins.
async function addVoucherCode(response, userId) {
try {
const key = "KEY_VOUCHER_CODE";
let cachedData = await redis.get(key);
if (cachedData) {
if (cachedData.userId === userId) response.voucherCode = cachedData.voucherCode;
return;
}
const voucherCode = await createVoucherCode(userId); //call to create voucher code and save to db
if (!voucherCode) return;
await redis.setEx(key, 15 * 60, {userId, voucherCode});
response.voucherCode = cachedData.voucherCode;
} catch (err) {
console.error("[Error] addVoucherCode: ", err);
}
}
I created a function that mimics a simultaneous request, and when I checked the response, all them have a voucher code, not just the first.
async function getProducts(url, params) {
try {
const customers = [
{ id: 1, token: "Bearer eyJhbGciOi....1" },
{ id: 2, token: "Bearer eyJhbGciOi....2"},
{ id: 3, token: "Bearer eyJhbGciOi....3"},
{ id: 4, token: "Bearer eyJhbGciOi....4"}
];
const data = await Promise.all(customers.map( async customer => {
return await fetch(url + "?" + params.toString(), {
headers: {
Authorization: customer.token
},
}).then(res => res.json());
}));
data.forEach((item, indx) => {
if(item.voucherCode) {
const id = customers[indx].id;
console.log(`Customer ${id} has a voucher!!!!!!!!!!!!!`)
}
})
} catch (err) {
console.error("[Error] getProducts: ", err);
}
}
Result
Customer 1 has a voucher!!!!!!!!!!!!!
Customer 2 has a voucher!!!!!!!!!!!!!
Customer 3 has a voucher!!!!!!!!!!!!!
Customer 4 has a voucher!!!!!!!!!!!!!
I tried adding a 200ms delay inside addVoucherCode but same result. Thanks in advance for the help.

You are calling addVoucherCode in a sync loop, so it'll run 4 times in parallel (and the 4 GET commands will be issued at the same time, it'll reply with null to all of them, and all of them will call createVoucherCode).
There are 2 things you can do to fix it:
Cache the promise of createVoucherCode:
const createVoucherCodePromises = new Map();
function createVoucherCode(userId) {
if (!createVoucherCodePromises.has(userId)) {
createVoucherCodePromises.set(
userId,
_createVoucherCode(userId)
.finally(() => createVoucherCodePromises.delete(userId))
);
}
return createVoucherCodePromises.get(userId);
}
async function _createVoucherCode(userId) {
// ...
}
NOTE: this will not solve the problem if you have multiple node processes running at the same time.
Use SET with NX (won't override existing values) and GET (return existing/old value)
> SET key voucher1 NX GET
OK
> SET key voucher2 NX GET # will return the existing value without overriding it
"voucher1"
> GET key
"voucher1"

Related

NodeJs parallel request in a loop too slow

so I have this feature that I am working on that takes an object (Over 10k items) picks an item from this object, sends it to an api, which process it then give a response, then proceeds to the next item.
Currently using the async library, the mapLimit method and it works as aspected.
But my problem is that it’s takes too long to loop through the entire dataset cause of the length.
This feature is suppose to be a continual process, once the entire object is iterated, wait for few seconds then do the same thing again.
I tried forking a child_process for this, broke the objects into chunks and created a process for each chunks till it was completed. This worked well as intended but the memory consumption was massive other processes on the server failed as a result of lack of available memory, even after exiting the process after it was completed.
Please how do I achieve this at a faster rate?
I use this to get the list of wallets.
getListofWallet = async () => {
try {
const USDT = await usdt.query(sql`
SELECT * FROM USDT ORDER BY id DESC;
`);
let counter = 0;
let completion = 0;
async.mapLimit(USDT, 6, async (user) => {
let userDetail = {
email: user.email,
id: user.user_id,
address: user.address
}
try {
await this.getWalletTransaction(userDetail);
completion++;
} catch (TronGridException) {
completion++;
console.log(":: A TronGrid Exception Occured");
console.log(TronGridException);
}
if (USDT.length == completion || USDT.length == (completion-5)) {
setTimeout(() => {
this.getListofWallet();
}, 60000);
console.log('~~~~~~~ Finished Wallet Batch ~~~~~~~~~');
}
}
);
} catch (error) {
console.log(error);
console.log('~~~~~~~Restarting TronWallet File after Crash ~~~~~~~~~');
this.getListofWallet();
}
}
Then I use this to process to data sent and perform the neccessary action.
getWalletTransaction = async (walletDetail) => {
const config = {
headers: {
'TRON-PRO-API-KEY': process.env.api_key,
'Content-Type': 'application/json'
}
};
const getTransactionFromAddress = await axios.get(`https://api.trongrid.io/v1/accounts/${walletDetail.address}/transactions/trc20`, config);
const response = getTransactionFromAddress.data;
const currentTimeInMillisecond = 1642668127000; //1632409548000
response.data.forEach(async (value) => {
if (value.block_timestamp >= currentTimeInMillisecond && value.token_info.address == "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t") {
let doesHashExist = await transactionCollection.query(sql`SELECT * FROM transaction_collection WHERE txhash=${value.transaction_id};`);
if (doesHashExist.length == 0) {
if (walletDetail.address == value.to) {
const checkExistence = await CryptoTransactions2.query(sql`
SELECT * FROM CryptoTransaction2 WHERE txHash=${value.transaction_id};
`);
if (checkExistence.length == 0) {
const xCollection = {
collection: "CryptoTransaction2",
queryObject: {
currency: "USDT",
sender: ObjectID("60358d21ec2b4b33e2fcd62e"),
receiver: ObjectID(walletDetail.id),
amount: parseFloat(tronWeb.fromSun(value.value)),
txHash: value.transaction_id,
description: "New wallet Deposit " + "60358d21ec2b4b33e2fcd62e" + " into " + value.to,
category: "deposit",
createdAt: new Date(),
updatedAt: new Date(),
},
};
await new MongoDbService().createTransaction(xCollection);
//create record inside cryptotransactions db.
await CryptoTransactions2.query(sql`INSERT INTO CryptoTransaction2 (txHash) VALUES (${value.transaction_id})`)
});
}

Creating and Capturing payment with paypal v2 node.js

I am trying to integrate PayPal server-side payment on a website with a new v2 since v1 is deprecated. Since I had to include quite a lot of code from their git, i will post here everything for a working example
In v1 i could do it really easily:
app.get('/create', function (req, res) {
//build PayPal payment request
let payReq = JSON.stringify({
'intent': 'sale',
'redirect_urls': {
'return_url': 'http://localhost:8081/process',
'cancel_url': 'http://localhost:8081/cancel'
},
'payer': {
'payment_method': 'paypal'
},
'transactions': [{
'amount': {
'total': '7.47',
'currency': 'USD'
},
'description': 'This is the payment transaction description.'
}]
});
paypal.payment.create(payReq, function (error, payment) {
if (error) {
console.error(error);
} else {
//capture HATEOAS links
let links = {};
payment.links.forEach(function (linkObj) {
links[linkObj.rel] = {
'href': linkObj.href,
'method': linkObj.method
};
})
//if redirect url present, redirect user
if (links.hasOwnProperty('approval_url')) {
res.redirect(links['approval_url'].href);
} else {
console.error('no redirect URI present');
}
}
});
});
app.get('/process', function (req, res) {
let paymentId = req.query.paymentId;
let payerId = {'payer_id': req.query.PayerID};
paypal.payment.execute(paymentId, payerId, function (error, payment) {
if (error) {
console.error(error);
} else {
if (payment.state == 'approved') {
const payerCountry = payment.payer.payer_info.country_code;
const total = payment.transactions[0].amount.total;
const currency = payment.transactions[0].amount.currency;
savePayment(payerCountry, total, currency);
res.send('payment completed successfully ' + cnt++);
} else {
res.send('payment not successful');
}
}
});
});
The create endpoint basically creates the order, the paypal API returns hateos links and the controller says the browser should redirect to that link. Once redirected , if user approves payment on paypal site he is redirected to on of
'redirect_urls': {
'return_url': 'http://localhost:8081/process',
'cancel_url': 'http://localhost:8081/cancel'
},
process endpoints retrieves PAYMENT ID and PAYER ID from query and captures the order - allowing me to do whatever i want to do ( e.g save payment in db ) in callback.
Now v2 seems like huge mess:
Following devs guide i have created
Paypal button (copy paste ):
import React, {useEffect} from "react";
import {PayPalButtons, PayPalScriptProvider, usePayPalScriptReducer} from "#paypal/react-paypal-js";
// This values are the props in the UI
const amount = "2";
const currency = "USD";
const style = {"layout": "vertical"};
const ButtonWrapper = ({currency, showSpinner}) => {
// usePayPalScriptReducer can be use only inside children of PayPalScriptProviders
// This is the main reason to wrap the PayPalButtons in a new component
const [{options, isPending}, dispatch] = usePayPalScriptReducer();
useEffect(() => {
dispatch({
type: "resetOptions",
value: {
...options,
currency: currency,
},
});
}, [currency, showSpinner]);
const createOrder = (data, actions) => {
console.log("create")
return fetch('http://localhost:8081/create', {
method: 'post'
}).then(function (res) {
return res.json();
}).then(function (orderData) {
console.log(orderData);
window.location = orderData.redirect;
});
}
// Call your server to finalize the transaction
const onApprove = (data, actions) => {
console.log("approve")
return fetch('/process', {
method: 'post'
}).then(function (res) {
return res.json();
}).then(function (orderData) {
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show confirmation or thank you
// This example reads a v2/checkout/orders capture response, propagated from the server
// You could use a different API or structure for your 'orderData'
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
return actions.restart(); // Recoverable state, per:
// https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
return alert(msg); // Show a failure message (try to avoid alerts in production environments)
}
// Successful capture! For demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
var transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction ' + transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
});
}
return (<>
{(showSpinner && isPending) && <div className="spinner"/>}
<PayPalButtons
style={style}
disabled={false}
forceReRender={[amount, currency, style]}
fundingSource={undefined}
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
/>
</>
);
}
export default function PayPalButton() {
return (
<div style={{ maxWidth: "750px", minHeight: "200px" }}>
<PayPalScriptProvider
options={{
"client-id": "test",
components: "buttons",
currency: "USD"
}}
>
<ButtonWrapper
currency={currency}
showSpinner={false}
/>
</PayPalScriptProvider>
</div>
);
}
And then followed the flow from paypal's github example
created their HttpClient
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* Returns PayPal HTTP client instance with environment which has access
* credentials context. This can be used invoke PayPal API's provided the
* credentials have the access to do so.
*/
function client() {
return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}
/**
* Setting up and Returns PayPal SDK environment with PayPal Access credentials.
* For demo purpose, we are using SandboxEnvironment. In production this will be
* LiveEnvironment.
*/
function environment() {
let clientId = process.env.PAYPAL_CLIENT_ID || '<clientId>';
let clientSecret = process.env.PAYPAL_CLIENT_SECRET || '<secret>';
if (process.env.NODE_ENV === 'production') {
return new checkoutNodeJssdk.core.LiveEnvironment(clientId, clientSecret);
}
return new checkoutNodeJssdk.core.SandboxEnvironment(clientId, clientSecret);
}
async function prettyPrint(jsonData, pre=""){
let pretty = "";
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
for (let key in jsonData){
if (jsonData.hasOwnProperty(key)){
if (isNaN(key))
pretty += pre + capitalize(key) + ": ";
else
pretty += pre + (parseInt(key) + 1) + ": ";
if (typeof jsonData[key] === "object"){
pretty += "\n";
pretty += await prettyPrint(jsonData[key], pre + "\t");
}
else {
pretty += jsonData[key] + "\n";
}
}
}
return pretty;
}
module.exports = {client: client, prettyPrint:prettyPrint};
created their CreateOrder:
/**
* PayPal SDK dependency
*/
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* PayPal HTTP client dependency
*/
const payPalClient = require('./PayPalClient');
/**
* Setting up the JSON request body for creating the Order. The Intent in the
* request body should be set as "CAPTURE" for capture intent flow.
*
*/
function buildRequestBody() {
return {
"intent": "CAPTURE",
"application_context": {
"return_url": "http://localhost:8081/process",
"cancel_url": "https://www.example.com",
"locale": "en-US",
"landing_page": "BILLING",
"user_action": "CONTINUE"
},
"purchase_units": [
{
"soft_descriptor": "Donation",
"amount": {
"currency_code": "USD",
"value": "220.00"
}
}
]
};
}
/**
* This is the sample function which can be sued to create an order. It uses the
* JSON body returned by buildRequestBody() to create an new Order.
*/
async function createOrder(debug=false) {
try {
const request = new checkoutNodeJssdk.orders.OrdersCreateRequest();
request.headers["prefer"] = "return=representation";
request.requestBody(buildRequestBody());
const response = await payPalClient.client().execute(request);
if (debug){
console.log("Status Code: " + response.statusCode);
console.log("Status: " + response.result.status);
console.log("Order ID: " + response.result.id);
console.log("Intent: " + response.result.intent);
console.log("Links: ");
response.result.links.forEach((item, index) => {
let rel = item.rel;
let href = item.href;
let method = item.method;
let message = `\t${rel}: ${href}\tCall Type: ${method}`;
console.log(message);
});
console.log(`Gross Amount: ${response.result.purchase_units[0].amount.currency_code} ${response.result.purchase_units[0].amount.value}`);
// To toggle print the whole body comment/uncomment the below line
console.log(JSON.stringify(response.result, null, 4));
}
return response;
}
catch (e) {
console.log(e)
}
}
/**
* This is the driver function which invokes the createOrder function to create
* an sample order.
*/
if (require.main === module){
(async() => await createOrder(true))();
}
/**
* Exports the Create Order function. If needed this can be invoked from the
* order modules to execute the end to flow like create order, retrieve, capture
* and refund(Optional)
*/
module.exports = {createOrder:createOrder};
And endpoints:
const createUsersOrder = async (res) => {
try {
let response = await createOrder();
console.log("Creating Order...");
let orderId = "";
if (response.statusCode === 201){
console.log("Created Successfully");
orderId = response.result.id;
console.log("Links:");
response.result.links.forEach((item, index) => {
let rel = item.rel;
let href = item.href;
let method = item.method;
let message = `\t${rel}: ${href}\tCall Type: ${method}`;
console.log(message);
});
let links = {};
response.result.links.forEach(function (linkObj) {
links[linkObj.rel] = {
'href': linkObj.href,
'method': linkObj.method
};
})
//if redirect url present, redirect user
if (links.hasOwnProperty('approve')) {
var returnObj = {redirect : links['approve'].href}
console.log("Returning " + returnObj)
res.send(returnObj);
} else {
console.error('no redirect URI present');
}
}
console.log("Copy approve link and paste it in browser. Login with buyer account and follow the instructions.\nOnce approved hit enter...");
return
} catch (error) {
console.log('There was an error: ', error);
}
};
app.post("/create", function(req,res) {
createUsersOrder(res);
})
Here, this is called when button is clicked, as "createOrder" method is called. I create order just like in the v1 code, and then redirect browser to the url. However when the user approves transaction on paypal, and thus is being redirected to one of
"application_context": {
"return_url": "http://localhost:8081/process",
"cancel_url": "http://localhost:8081/cancel",
"locale": "en-US",
"landing_page": "BILLING",
"user_action": "CONTINUE"
},
return url ( /process route for success ), the request DOES NOT contain payment_id, only PAYER_ID. But the payment_id ( or order_id in v2 ) is needed to capture the order.
Since i have found literally zero blogs, tutorials, guide for v2 ( only millions for v1) i am confused where to get the order id. Do i really need to save it in DB after i create the order? OR is there any other trick?
Also, the button contains onApprove method, but after creating order, the browser is redirected to paypal, and the paypal redirects user to http://localhost:8081/process endpoint - thus the onApprove method is never invoked and useless (?).
This whole flow of v2 is really confusing, is there something i am missing?
Thanks for help
With your v2 code, do not use any redirects. At all. Your button should call 2 endpoints on your server. These two endpoints should (respectively) do the API operations of creating and capturing the order, and return the JSON result in each case (the capture route can do any server-side operations like storing the transaction result in the database before forwarding the JSON result to the client caller, since the client needs to handle any capture error situations as well as showing a success message). You can find a full stack node.js example in the PayPal integration guide, but it's fine to keep your #paypal/react-paypal-js code pretty much as-is for the front end.

Issue on asynchronous implementation in NodeJS with Sequelize

I have a scenario where the API will accept the request body as below and I have failed to achieve asynchronous operation. The scenario is the API should only accept the paid amount of the particular item that has not submitted more than the existing balance which has been stored in the database.
{
"items": [
{
"itemId": "3c6d9287-a247-4852-b168-29f766e073fb",
"paidAmount": 5
},
{
"itemId": "8f3ebe13-b4dd-4b5c-887c-9663b29065cd",
"paidAmount": 5.80
}
]
}
The expected execution flow will be:
Compare the body request with the existing data from the database.
Perform the subtraction outstandingBlc (which handled by sequelize function) and paidamount of the body request.
If the substraction value is less than 0, the API should then return an error message back to the caller. Otherwise, it will continually perform the subsequent looping.
I have encountered any issue to capture the final value of the allAccepted variable (which shown in the last 3 line of the below code) after a sequence of execution inside the looping.
var data = req.body;
var items = data.items;
var total = items.map(itm =>{ return itm.paidAmount; }).reduce((a, b) => a + b, 0).toFixed(2);
var blc = 0.00;
const balance = await checkBalance(req,res);
if (balance != null){
blc = balance;
}
if (blc < total){
return res.status(403).json({error:"Insufficient balance to complete the payment!"});
}
var allAccepted = false;
Promise.all(items.map(v=>{
OrderItemTransaction.findOne({
where:{itemId:v.itemId},
attributes: [
[db.Sequelize.literal('(OrderItem.unitPrice - SUM(paidAmount))'), 'outstandingBlc']
],
include:[{
model: OrderItem,
on: {
'id': { [Op.eq]: db.sequelize.col('OrderItemTransaction.itemId') }
},
required: true
}],
raw:true
}).then(itemInfo=>{
if (!itemInfo){
return void res.status(404).json({error:"Invalid item Id"});
}
else{
const reqPaidAmount = v.paidAmount;
const outstandingBalance = itemInfo.outstandingBlc;
console.log(reqPaidAmount +"|" + outstandingBalance);
if (outstandingBalance - reqPaidAmount < 0){
allAccepted = false;
return res.status(403).json({error:`Cannot complete payment for ${v.itemId} because paid amount is ${reqPaidAmount} and outstanding balance is ${outstandingBalance}`})
}
else{
allAccepted = true;
}
}
}).catch(err=>{
console.log(err);
return res.status(500).json({error:err.message});
});
}));
console.log(allAccepted);
if (allAccepted == true){
return res.status(200).json({total:total,blc:blc});
}
Last 3 lines of code doesn't wait till the execution of promise.all
Add then block as below.
Promise.all(
items.map(v => {
// your db operations code
})
).then(promiseAllResult => {
if (allAccepted == true) {
return res.status(200).json({
total: total,
blc: blc
});
}
})

Do node js worker never times out?

I have an iteration that can take up to hours to complete.
Example:
do{
//this is an api action
let response = await fetch_some_data;
// other database action
await perform_operation();
next = response.next;
}while(next);
I am assuming that the operation doesn't times out. But I don't know it exactly.
Any kind of explanation of nodejs satisfying this condition is highly appreciated. Thanks.
Update:
The actual development code is as under:
const Shopify = require('shopify-api-node');
const shopServices = require('../../../../services/shop_services/shop');
const { create } = require('../../../../controllers/products/Products');
exports.initiate = async (redis_client) => {
redis_client.lpop(['sync'], async function (err, reply) {
if (reply === null) {
console.log("Queue Empty");
return true;
}
let data = JSON.parse(reply),
shopservices = new shopServices(data),
shop_data = await shopservices.get()
.catch(error => {
console.log(error);
});
const shopify = new Shopify({
shopName: shop_data.name,
accessToken: shop_data.access_token,
apiVersion: '2020-04',
autoLimit: false,
timeout: 60 * 1000
});
let params = { limit: 250 };
do {
try {
let response = await shopify.product.list(params);
if (await create(response, shop_data)) {
console.log(`${data.current}`);
};
data.current += data.offset;
params = response.nextPageParameters;
} catch (error) {
console.log("here");
console.log(error);
params = false;
};
} while (params);
});
}
Everything is working fine till now. I am just making sure that the execution will ever happen in node or not. This function is call by a cron every minute, and data for processing is provided by queue data.

nodeJS: how to call an async function within a loop in another async function call

I am trying to call one async function from inside a loop run by another async function. These functions call APIs and I am using request-promise using nodeJS.
functions.js file
const rp = require("request-promise");
// function (1)
async email_views: emailId => {
let data = {};
await rp({
url: 'myapiurl',
qs: { accessToken: 'xyz', emailID: emailId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above JSON looks like this:
...
data:{
records: [
{
...
contactID: 123456,
...
},
{
...
contactID: 456789,
...
}
]
}
...
I am running a loop to get individual record, where I am getting a contactID associated with each of them.
// function#2 (also in functions.js file)
async contact_detail: contactId => {
let data = {};
await rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above function takes one contactId as parameter and gets that contact's detail calling another API endpoint.
Both functions work fine when they are called separately. But I am trying to do it inside a loop like this:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId); // function#2
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...
The problem is I am only getting the first contact back from the above code block, i.e. even I have 20 records from function#1, in the loop when I am calling contact_detail (function#2) for each contactID (cId), I get contact detail once, i.e. for the first cId only. For rest I get nothing!
What is the correct way to achieve this using nodeJs?
UPDATE:
const { App } = require("jovo-framework");
const { Alexa } = require("jovo-platform-alexa");
const { GoogleAssistant } = require("jovo-platform-googleassistant");
const { JovoDebugger } = require("jovo-plugin-debugger");
const { FileDb } = require("jovo-db-filedb");
const custom = require("./functions");
const menuop = require("./menu");
const stateus = require("./stateus");
const alexaSpeeches = require("./default_speech");
const app = new App();
app.use(new Alexa(), new GoogleAssistant(), new JovoDebugger(), new FileDb());
let sp = "";
async EmailViewsByContactIntent() {
try {
const viewEmailId =
this.$session.$data.viewEmailIdSessionKey != null
? this.$session.$data.viewEmailIdSessionKey
: this.$inputs.view_email_Id_Number.value;
let pageIndex =
this.$session.$data.viewEmailPageIndex != null
? this.$session.$data.viewEmailPageIndex
: 1;
const result = await custom.email_views_by_emailId(
viewEmailId,
pageIndex
);
const records = result.data.records;
if (records.length > 0) {
const totalRecords = result.data.paging.totalRecords;
this.$session.$data.viewEmailTotalPages = totalRecords;
sp = `i have found a total of ${totalRecords} following view records. `;
if (totalRecords > 5) {
sp += `i will tell you 5 records at a time. for next 5 records, please say, next. `;
this.$session.$data.viewEmailIdSessionKey = this.$inputs.view_email_Id_Number.value;
this.$session.$data.viewEmailPageIndex++;
}
for (let i = 0; i < records.length; i++) {
const r = records[i];
/* Here I want to pass r.contactID as contactId in the function contact_detail like this: */
const contact = await custom.contact_detail(r.contactID);
const contact_name = contact.data.firstName + " " + contact.data.lastName;
/* The above two lines of code fetch contact_name for the first r.contactID and for the rest I get an empty string only. */
const formatted_date = r.date.split(" ")[0];
sp += `contact ID ${spellOut_speech_builder(
r.contactID
)} had viewed on ${formatted_date} from IP address ${
r.ipAddress
}. name of contact is, ${contact_name}. `;
}
if (totalRecords > 5) {
sp += ` please say, next, for next 5 records. `;
}
} else {
sp = ``;
}
this.ask(sp);
} catch (e) {
this.tell(e);
}
}
I am building an alexa skill using JOVO framework and nodeJS.
UPDATE #2
As a test, I only returned the contactId which I am passing to the contact_detail function and I am getting the correct value back to the above code under my first UPDATE.
async contact_detail: contactId => {
return contactId;
}
It seems even after getting the value right, the function is somehow failing to execute. However, the same contact_detail function works perfectly OK, when I am calling it from another place. Only doesn't not work inside a loop.
What could be the reason?
I must be missing something but don't know what!
You are mixing async await and promises together which is causing you confusion. You typically would use one of the other(as async await effectivly provides syntax sugar so you can avoid dealing with the verbose promise code) in a given location.
Because you mixed the two you are in a weird area where the behavior is harder to nail down.
If you want to use async await your functions should look like
async contact_detail: contactId => {
try {
const body = await rp({
url: 'myapiurl2',
qs: { ... }
});
return JSON.parse(body);
} catch(e) {
console.log(e);
//This will return undefined in exception cases. You may want to catch at a higher level.
}
};
or with promises
async contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { ... }
})
.then( body => JSON.parse(body))
.catch( error => {
console.log(error);
//This will return undefined in exception cases. You probably dont want to catch here.
});
};
Keep in mind your current code executing the function will do each call in series. If you want to do them in parallel you will need to call the function a bit differently and use something like Promise.all to resolve the result.
Here you go:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
await Promise.all(records.map(async record => {
let cId = record.contactID;
let contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
});
console.log(names);
...
I'm posting this as an answer only because I need to show you some multi-line code as part of throubleshooting this. Not sure this solves your issue yet, but it is a problem.
Your contact_detail() function is not properly returning errors. Instead, it eats the error and resolves with an empty object. That could be what is causing your blank names. It should just return the promise directly and if you want to log the error, then it needs to rethrow. Also, there's no reason for it to be declared async or to use await. You can just return the promise directly. You can also let request-promise parts the JSON response for you too.
Also, I notice, there appears to be a syntax error in your .catch() which could also be part of the problem.
contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
json: true,
method: 'GET'
}).catch( error => {
// log error and rethrow so any error propagates
console.log(error);
throw error;
});
};
Then, you would call this like you originally were (note you still use await when calling it because it returns a promise):
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...

Resources