Saving and Reading UserState in Botframework v4 - node.js

Hello I'm having a hard time dealing with UserStates in MSBF
Here's the setup of the dialogBot.ts
export class DialogBot extends ActivityHandler {
private conversationState: BotState;
private userState: BotState;
private dialog: Dialog;
private dialogState: StatePropertyAccessor<DialogState>;
/**
*
* #param {BotState} conversationState
* #param {BotState} userState
* #param {Dialog} dialog
*/
constructor(
conversationState: BotState,
userState: BotState,
dialog: Dialog
) {
super();
if (!conversationState) {
throw new Error(
'[DialogBot]: Missing parameter. conversationState is required'
);
}
if (!userState) {
throw new Error('[DialogBot]: Missing parameter. userState is required');
}
if (!dialog) {
throw new Error('[DialogBot]: Missing parameter. dialog is required');
}
this.conversationState = conversationState as ConversationState;
this.userState = userState as UserState;
this.dialog = dialog;
this.dialogState =
this.conversationState.createProperty<DialogState>('DialogState');
this.onMessage(async (context, next) => {
console.log('Running dialog with Message Activity.');
// Run the Dialog with the new message Activity.
await (this.dialog as MainDialog).run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onDialog(async (context, next) => {
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
In the MainDialog.ts I'm fetching a user from the database based on the userID passed on and if it fetches anything it should be saved in the UserState.
mainDialog.ts
export class MainDialog extends CancelAndHelpDialog {
private userProfileAccessor: StatePropertyAccessor<any>;
userState: UserState;
constructor(
bookingDialog: BookingDialog,
userState: UserState,
conversationState: ConversationState
) {
super('MainDialog');
// DECLARE DIALOGS HERE
const createJobOrderDialog = new CreateJobOrderDialog(
'createJobOrderDialog'
);
const checkJobOrderStatusDialog = new CheckJobOrderStatusDialog(
'checkJobOrderStatusDialog'
);
const accountSetupDialog = new AccountSetupDialog(
'accountSetupDialog',
userState
);
this.userProfileAccessor = userState.createProperty('userProfile');
this.userState = userState;
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new TextPrompt('TextPrompt'));
this.addDialog(bookingDialog);
this.addDialog(createJobOrderDialog);
this.addDialog(checkJobOrderStatusDialog);
this.addDialog(accountSetupDialog);
this.addDialog(
new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.accountSetupStep.bind(this),
this.introStep.bind(this),
this.actStep.bind(this),
this.finalStep.bind(this)
])
);
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a DialogContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {TurnContext} context
*/
public async run(
context: TurnContext,
accessor: StatePropertyAccessor<DialogState>
) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(context);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
private async accountSetupStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
stepContext.context.activity.from.id = '*******************';
userProfile.isHandover = false;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
const result = await userService.getUser(
stepContext.context.activity.from.id
);
console.log(result);
if (Object.keys(result).length === 0) {
return await stepContext.beginDialog('accountSetupDialog');
} else {
userProfile.user = result;
await this.userProfileAccessor.set(stepContext.context, userProfile);
// await this.userState.saveChanges(stepContext.context, true);
return await stepContext.next();
}
}
private async introStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
const userProfile = await this.userProfileAccessor.get(
stepContext.context,
{}
);
console.log('INTRO STEP USERPROFILE', userProfile);
await stepContext.context.sendActivities([
{
type: 'message',
text: `Hi ${userProfile.user.first_name}, welcome to Podmachine. Let us take care of the dirty stuff so you can sound like a Pro!`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: 'To start, you need to submit a job order.'
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `So what's a job order? It's basically sending a request to edit (1) one raw episode audio file to Podmachine team. We'll handle the rest. `
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 },
{
type: 'message',
text: `Since you're part of the early access users (Yay!), you're entitled to (1) one free job order / edit. Go ahead and click "Create New Job order."`
},
{
type: 'typing'
},
{ type: 'delay', value: 1000 }
]);
const messageText = (stepContext.options as any).restartMsg
? (stepContext.options as any).restartMsg
: `Please take note that once you submit your job order, Podmachine team will review it first. Make sure all the details you put in your job order are correct. It will be our basis when we do the edits. Thank you!`;
const promptMessage = MessageFactory.suggestedActions(
[
'Create New Job Order',
'Check Status',
'Chat with Team',
'Subscribe Now'
],
messageText
);
return await stepContext.prompt('TextPrompt', {
prompt: promptMessage
});
}
/**
* Second step in the waterall. This will use LUIS to attempt to extract the origin, destination and travel dates.
* Then, it hands off to the bookingDialog child dialog to collect any remaining details.
*/
private async actStep(
stepContext: WaterfallStepContext
): Promise<DialogTurnResult> {
// const bookingDetails = new BookingDetails();
const userProfile = await this.userProfileAccessor.get(stepContext.context, {});
console.log('USER PROFILE ACT STEP', userProfile);
switch (stepContext.result) {
case 'Create New Job Order':
return await stepContext.beginDialog('createJobOrderDialog');
break;
case 'Check Status':
return await stepContext.beginDialog('checkJobOrderStatusDialog');
break;
case 'Chat with Team':
userProfile.isHandover = true;
await stepContext.context.sendActivity(
`Hi ${userProfile.user.first_name}, we're glad to assist you. Please type your concern below. A Podmachine associate will getback to you within 3-5 minutes. Thank you for your patience.`
);
await this.userProfileAccessor.set(stepContext.context, userProfile);
return await stepContext.endDialog();
break;
case 'Upgrade Now':
await stepContext.context.sendActivity(
`Redirecting to Upgrade Now page...`
);
return await stepContext.endDialog();
break;
case 'Schedule a Checkpoint Meeting':
await stepContext.context.sendActivity(`Feature in progress...`);
return await stepContext.endDialog();
break;
default:
break;
}
return await stepContext.next();
// return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
I can see the saved user details in the introStep but when it comes to the actStep I no longer see the value and it comes out undefined. Can you help me with implementing UserState because I'm not sure if I'm doing it correctly by loading it, the samples from github is not as clear.
USER PROFILE ACT STEP {}
[onTurnError] unhandled error: DialogContextError: Cannot read properties of undefined (reading 'first_name')

Looks like your bot aren't storing the state, so it can't recover it on the next turn.
Are you setting somewhere the storage your bot are using?
Check this doc on how to use storages:
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript

Related

Cancel data insert inside beforeInsert event if it already exists in table

There is a need to insert multiple rows in a table. I am using TypeOrm which has .save() method.
.save() method is used to insert data in bulk, and it checks for the inserting primary key ID. If they exist it updates, otherwise inserts.
I want to do the checking on some other field shortLInk. Which is not possible with .save()
So I tried to do inside beforeInsert() event, things are fine but I need to cancel the isnertions if row is find.
Is there any way to achieve it? I couldn't find anything in documentation.
I can throw an error inside beforeInsert() but it will cancel whole insertions.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
return this.quickLinkRepository.save(quickLinks, {});
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
return delete event.entity; // throws error
}
event.entity.shortLink = shortLink;
}
}
When you use a transaction, all the operations are atomic, meaning either all of them are executed or none of them are. you can check for the existence of the shortLink before the insert and if it exists, you can cancel the whole transaction.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
const queryRunner = this.quickLinkRepository.manager.connection.createQueryRunner();
try {
await queryRunner.startTransaction();
const result = await this.quickLinkRepository.save(quickLinks, {});
await queryRunner.commitTransaction();
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
throw new Error('The short link already exists');
}
event.entity.shortLink = shortLink;
}
}

How to pass additional params to Stripe Custom Flow?

Following Stripes official tutorial to create payment form with apple pay support: https://stripe.com/docs/payments/quickstart#collect-billing-address-details
But on thank you page, i need to know: user email and stripe customer id. How i can pass these parameters to thank you page?
My code for prepare.php:
$productID = $request['items'][0]['id'];
$customer = \Stripe\Customer::create();
$paymentIntent = \Stripe\PaymentIntent::create([
'customer' => $customer->id,
'setup_future_usage' => 'off_session',
'amount' => config('app.PRICE_TNT6WEEK') * 100, //6week program
'currency' => 'usd',
'automatic_payment_methods' => [
'enabled' => true,
],
'description' => $productID,
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
\Log::debug(['<pre>'.print_r($request->toArray(), true).'</pre>', $productID, $output]);
return response()->json($output);
My checkout.js:
// This is your test publishable API key.
const stripe = Stripe('{{config('app.STRIPE_KEY')}}');
// The items the customer wants to buy
const items = [{ id: "{{$productName}}"}];
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("{{route('prepare.product', [$slug, $slugVersion])}}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElementOptions = {
layout: "tabs",
};
const paymentElement = elements.create("payment", paymentElementOptions);
paymentElement.mount("#payment-element");
// Create and mount the Address Element in billing mode
const addressElement = elements.create("address", {
mode: "billing",
defaultValues: {
name: 'Your Full Name...',
address: {
line1: 'Address...',
city: 'City...',
state: 'CA',
postal_code: '',
country: 'US',
},
},
fields: {
phone: 'always',
},
});
addressElement.mount("#address-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
//--- There we should make an additional ajax request with user data
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "{{route('upsell.show', [$slug, $slugVersion])}}",
payment_method_data: {
billing_details: {
name: document.getElementById("full_name").value,
email: document.getElementById("email").value,
phone: document.getElementById("phone").value,
}
},
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
After collecting billing details with either the Address Element, or supplying the payment_method_data[billing_details], that information will be available on Payment Method billing_details (API ref). You can get this by either retrieving the Payment Method, or by retrieving the associated Payment Intent and using expansion to include the full Payment Method object with expand[]=payment_method

Repeating a dialog step based on validation

I'm currently building a provisioning bot using v4 of the Bot Framework and I've integrated it with the Microsoft Graph.
The Microsoft Graph is being used to validate user inputs, so in this scenario, it's checking to see if the group name already exists. However, the issue I'm running into is getting the bot to repeat the previous step if the validation finds the group exists.
I've read through the forum and seen a number of solutions, particularly, I have come across the step.activeDialog.state['stepIndex']-2 approach, but have been unable to get it to work. Is this a viable solution for going back a step in NodeJS or should I be looking at another approach?
async nameStep(step) {
// User selected a group type and now is required to enter the name of the group
step.values.sitetype = step.result.value;
return await step.prompt(NAME_PROMPT, 'What do you want to name it');
}
async ownerStep(step) {
// Retrieve the value from the previous step and check against the Microsoft Graph to see if the name has been used previously
step.values.name = step.result;
const getToken =
await axios.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
return {
headers: {
'Authorization': 'Bearer ' + response.data.access_token
}
}
})
.catch(error => {
console.log(error);
});
const graphCall =
await axios.get("https://graph.microsoft.com/v1.0/groups?$filter=startswith(displayName,'" + `${step.result}` + "')", getToken)
.then((response) => {
if (response.data.value[0] != null) {
return true;
}
})
.catch((error) => {
console.log(error);
})
if (!graphCall) {
return await step.prompt(NAME_PROMPT, 'What is your email address');
} else {
await step.context.sendActivity("Group already exists");
return await step.activeDialog.state['stepIndex']-2
}
}
Thanking you in advance
You can achieve this by use of a component dialog. Essentially, you extrapolate the steps you would like to repeat into a separate dialog that is called only from within the current (parent) dialog. In the parent, you institute your checks. When a check fails, the component dialog is called again. If it succeeds, the parent dialog continues on.
In the code below, my parent dialog immediately calls the component dialog for a first pass thru presenting the user with two options. Each will send a pre-determined text value which is checked to see if a LUIS intent exists for it.
The first option, "Hello", will succeed with an intent having been found. It then restarts the parent dialog. The parent dialog starts with the text "You have a choice to make in life..." which will re-display as the parent dialog begins again.
The second option will fail and returns the user to the component dialog to try again. The component dialog starts with "Text me something! I'll see if my maker setup a LUIS intent for it." This text will display when either button is clicked because the component dialog is run in both instances. However, only this text will display when LUIS fails to find an intent and restarts the component dialog.
Side note - the parent dialog in this example is, in fact, a component dialog to my main dialog which is why it is exported at the end. So, yes, you can have component dialogs within component dialogs.
Parent Dialog:
const { ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const { LuisRecognizer } = require('botbuilder-ai');
const { ChoiceDialogSub, CHOICE_DIALOG_SUB } = require('./choiceDialog_Sub');
const CHOICE_DIALOG = 'choiceDialog';
class ChoiceDialog extends ComponentDialog {
constructor(id) {
super(id);
this.addDialog(new ChoiceDialogSub(CHOICE_DIALOG_SUB));
this.addDialog(new WaterfallDialog(CHOICE_DIALOG, [
this.welcomeStep.bind(this),
this.choiceLuisStep.bind(this)
]));
this.initialDialogId = CHOICE_DIALOG;
try {
this.recognizer = new LuisRecognizer({
applicationId: process.env.LuisAppId,
endpointKey: process.env.LuisAPIKey,
endpoint: `https://${ process.env.LuisAPIHostName }`
}, {}, true);
} catch (err) {
console.warn(`LUIS Exception: ${ err } Check your LUIS configuration`);
}
}
async welcomeStep(stepContext) {
await stepContext.context.sendActivity('You have a choice to make in life...');
return await stepContext.beginDialog(CHOICE_DIALOG_SUB);
}
async choiceLuisStep(stepContext) {
if (stepContext.context.activity.text) {
const stepResults = stepContext.context.activity.text;
const recognizerResult = await this.recognizer.recognize(stepContext.context);
const intent = await LuisRecognizer.topIntent(recognizerResult);
if (intent === 'Greeting') {
await stepContext.context.sendActivity(`'${ stepResults }' identified in the {${ intent }} intent.`);
return await stepContext.beginDialog(CHOICE_DIALOG);
} else {
await stepContext.context.sendActivity(`No LUIS intent was found for '${ stepResults }'.`);
return await stepContext.beginDialog(CHOICE_DIALOG_SUB);
}
} else {
await stepContext.context.sendActivity('I need text, fool!');
return await stepContext.next();
}
}
}
module.exports.ChoiceDialog = ChoiceDialog;
module.exports.CHOICE_DIALOG = CHOICE_DIALOG;
Component Dialog:
const { ChoicePrompt, ChoiceFactory, ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const CHOICE_DIALOG_SUB = 'choiceDialogSub';
const CHOICE_DIALOG_SUB_PROMPT = 'choicePromptSub';
class ChoiceDialogSub extends ComponentDialog {
constructor(id) {
super(id);
this.addDialog(new ChoicePrompt(CHOICE_DIALOG_SUB_PROMPT))
.addDialog(new WaterfallDialog(CHOICE_DIALOG_SUB, [
this.choiceStep.bind(this)
]));
this.initialDialogId = CHOICE_DIALOG_SUB;
}
async choiceStep(stepContext) {
const choices = ['Hello', 'No soup for you!'];
return await stepContext.prompt(CHOICE_DIALOG_SUB_PROMPT, {
prompt: "Text me something! I'll see if my maker setup a LUIS intent for it.",
choices: ChoiceFactory.toChoices(choices)
});
}
}
module.exports.ChoiceDialogSub = ChoiceDialogSub;
module.exports.CHOICE_DIALOG_SUB = CHOICE_DIALOG_SUB;
Hope of help!

getting undefined from async await in typescript node

Use case:
I am trying to insert a record inside the amazon QLDB using Node and typescript.
I am able to insert the record/document successfully and it returns me documentID in return.
there are 2 controllers: EntityController and CommonController
-EntityController extends CommonController
-EntityController has the code for getting req object converting it into the model object and the calling insert() function that has been extended from the CommonController.
problem
I am trying to propagate that documentID to all the way to my API call, but somehow I am getting undefined in the EntityController.
whereas I am able to print the documentID in CommonController.
I am not sure why I am getting undefined when I am clearly returning a value.
const CommonController = require("../template/controller");
import { Request, Response } from 'express';
const tableName:string = "entities";
const EntityModel = require("./model")
class EntityController extends CommonController {
async insertEntitiy(req:Request,res:Response) {
async insertEntitiy(req:any,res:any) {
console.log(req);
console.log("===========");
console.log(req.body);
let entity = new EntityModel();
entity.balance = req.body.balance;
entity.firstName = req.body.firstName;
entity.lastName = req.body.lastName;
entity.email = req.body.email;
try {
let documentIds = await this.insert(tableName,entity);
console.log("--------- inside insertEntity fiunction()---------");
console.log(documentIds);
console.log("------------------");
res.status(200).send(documentIds[0]);
} catch (error) {
console.error(`error in creating Entity: ${error}`);
res.status(500).send({ errMsg: `error in creating Entity: ${error}` });
}
}
}
module.exports = new EntityController();
import { createQldbWriter, QldbSession, QldbWriter, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
import { Reader } from "ion-js";
import { error, log } from "../qldb/LogUtil";
import { getFieldValue, writeValueAsIon } from "../qldb/Util";
import { closeQldbSession, createQldbSession } from "../qldb/ConnectToLedger";
module.exports = class Conroller {
async insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result:Array<string>;
try {
session = await createQldbSession();
await session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return (Promise.resolve(result));
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
/**
* Insert the given list of documents into a table in a single transaction.
* #param txn The {#linkcode TransactionExecutor} for lambda execute.
* #param tableName Name of the table to insert documents into.
* #param documents List of documents to insert.
* #returns Promise which fulfills with a {#linkcode Result} object.
*/
async insertDocument(
txn: TransactionExecutor,
tableName: string,
documents: object
): Promise<Array<string>> {
const statement: string = `INSERT INTO ${tableName} ?`;
const documentsWriter: QldbWriter = createQldbWriter();
let documentIds: Array<string> = [];
writeValueAsIon(documents, documentsWriter);
let result: Result = await txn.executeInline(statement, [documentsWriter]);
const listOfDocumentIds: Reader[] = result.getResultList();
listOfDocumentIds.forEach((reader: Reader, i: number) => {
documentIds.push(getFieldValue(reader, ["documentId"]));
});
console.log("---------documentIds---------");
console.log(documentIds);
console.log("------------------");
return (documentIds);
}
}
ouptut :
---------documentIds---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
---------result inside insert fiunction()---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
--------- inside insertEntity fiunction()---------
undefined
As #daniel-w-strimpel pointed out in the comments, your insert method returns only in the catch part.
Try this:
insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result: Array<string>;
try {
session = await createQldbSession();
return session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return result;
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
...
In return session.executeLambda you return the Promise.
In return result; you return the actual value.
More on promises here: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

sequelize ORM asynchronous method calls

How can I call methods asynchronously in sequelize ORM? (because I have to use returned value inside other methods).
user.dao.js:
var User = require('./user.model');
class UserDao {
constructor() {}
insert(user) {
var pk;
User.sync({ force: false }).then(() => {
User.create(user).then(function(user) {
console.log('Entry successful from dao: ' +
JSON.stringify(user));
//return generated pk
pk = user.id;
console.log('ID: ' + pk);
});
});
return pk;
}
user.test.js:
class UserDaoTest {
constructor() {
this.userDao = new UserDao();
this.compare = new UtilsObject();
}
/*
all CRUD method calls
*/
testAll() {
this.testInsert();
this.testUpdate();
//this.testDelete();
//this.testRead();
//this.compare();
}
/*
insert method
*/
testInsert() {
// composite form
var user = {
name: 'nisha',
email: 'nisha#gmail.com',
phoneNo: 8978,
picUrl: 'nisha',
description: 'SI',
status: 'active',
waitingTime: 10,
rating: 7
};
/*
calling insert user with above data
*/
var pk = this.userDao.insert(user);
console.log('pk value: ' + pk);
//var obj1 = this.userDao.readById(pk);
console.log('obj1 value: ' + user);
//this.testReadById(obj1);
}
testReadById(obj1) {
var obj2 = this.userDao.readById(obj1);
this.compare.compare(obj1, obj2);
this.testDelete(obj1);
}
}
export default UserDaoTest;
Here in user.test.js, in testInsert() method want to get the value of pk which is returned from insert() method of user.dao.js, but right now I am getting pk value as undefined.
Use a promise chain.
Suppose you need to get an entry for a particular user & do some operations on it.
Model.User.findById(someId)
.then((user) => {
// Do something with user.
})
You shouldn't be calling methods synchronously, NodeJs is not designed this way. It works with callbacks or promises.
Your code won't work because it is async code.
Watch the famous Youtube video about the event loop
But in short, if you will run the following example, which is like your code but without your logic:
var User = require('./user.model');
class UserDao {
constructor() {}
insert(user) {
var pk;
console.log('1');
User.sync({ force: false }).then(() => {
pk = 123;
console.log('3');
});
console.log('2');
return pk;
}
The variable pk will be undefined and your console will look like this:
1
2
3
If you want it to work, you should "wait" for the async functions like this:
var User = require('./user.model');
class UserDao {
constructor() {}
// #return Promise
insert(user) {
return User.sync({ force: false }).then(() => {
return User.create(user)
}).then((user) => {
console.log('Entry successful from dao: ' + JSON.stringify(user));
return user.id
})
}
And when you use it:
class UserDaoTest {
constructor() {
this.userDao = new UserDao();
this.compare = new UtilsObject();
}
/*
all CRUD method calls
*/
testAll() {
// if testInsert and testUpdate can run simultaneously you can keep it like this.
// Otherwise, use Promise.then as well
this.testInsert();
this.testUpdate();
}
/*
insert method
*/
testInsert() {
var user = {
// ...
};
/*
calling insert user with above data
*/
this.userDao.insert(user).then((userId) => {
// YOUR COMPARE CODE
}).then(done); // Where done is a function to let you test framework that you async code is done
}
}
export default UserDaoTest;
Another way of doing that is using the new async and await. That way you will get a code which is more readable and maintainable.
You can read more here

Resources