Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
667 views
in Technique[技术] by (71.8m points)

javascript - Cannot get conversationState property value inside a setTimeOut

I am using botBuilder SDK 4.3 for Node js.

I created a conversationState property inside the constructor of a dialog. In some of the dialog steps I set a value to that property.

In an other step, I tried to get the value of that property inside a setTimeOut like So.

// Imports ...

class Reservation extends ComponentDialog {
  constructor(id, conversationState, userState, dialogProps) {
    super(id);
    this.id = id;
    this.conversationState = conversationState;
    this.userState = userState;
    this.initialDialogId = CONFIGS.MAIN_DIALOG_ID;
    this.reservationNotifProp = conversationState.createProperty(
      "reservationNotif"
    );
    ... 
    this.addDialog(
      new WaterfallDialog(this.initialDialogId, [
        this.askCheckIn.bind(this),
        this.askCheckout.bind(this)
        this.askNights.bind(this),
        this.validateDates.bind(this),
        .....
      ]
    );
  }

  async askCheckIn (step) { ... }

  async askCheckout (step) { ... }

  async askNights (step) {
    // ...
    this.reservationNotifProp.set(step.context, false);
    await this.conversationState.saveChanges(step.context);
    const ref = this;
    setTimeout(async () => {
      const notif = await this.reservationNotifProp.get(step.context);
      if (notif) {
        console.log("Send Notif ...");
      }
    }, 50000);
  }

  async validateDates(step) {
    // ...
    this.reservationNotifProp.set(step.context, true);
    await this.conversationState.saveChanges(step.context);
  }
}

When the time out is finished, I got this error and notif is undefined:

(node:47504) UnhandledPromiseRejectionWarning: TypeError: Cannot perform 'get' on a proxy that has been revoked
    at ConversationState.load (c:UsersMontacerDesktopqt-bot
ode_modulesotbuilder
ode_modulesotbuilder-coresrcotState.ts:84:48)
    at BotStatePropertyAccessor.get (c:UsersMontacerDesktopqt-bot
ode_modulesotbuilder
ode_modulesotbuilder-coresrcotStatePropertyAccessor.ts:97:43)
    at Timeout.setTimeout [as _onTimeout] (c:UsersMontacerDesktopqt-botdialogs
eservation.js:366:63)
    at ontimeout (timers.js:498:11)
    at tryOnTimeout (timers.js:323:5)
    at Timer.listOnTimeout (timers.js:290:5)
warning.js:18
(node:47504) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
warning.js:18
(node:47504) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Any solutions ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

For some reason, the BotFramework does not work well with callbacks which is why you are getting the "Cannot perform 'get' on a proxy that has been revoked" error. A solution - yet complicated - to this is to create a proactive message API endpoint, call a request to it from the timeout callback, and then execute the rest of the bot calls from the proactive message. I would recommend taking a look at the Proactive Messaging Sample before getting started with code below.

index.js file

We are going to add an /api/notify endpoint to our Restify Server that will be hit when the timeout finishes. I would recommend adding a method to your bot to handle sending proactive messages, so you can keep all of the state and dialog elements contained in your bot instead of elevating them to the index file. Note, you will have to pass the adapter as a parameter to your bot.

let bot = new Bot(adapter, conversationState, userState);

...

server.get('/api/notify/:conversationId', (req, res) => {
    bot.sendProactiveMessages(req.params.conversationId);
    res.send(204);
});

Dialog

In this step of the dialog, we are adding a responded attribute to the user profile - you can also add this to conversation state - and setting the default value to false. Then instead of configuring the callback to access the state and message the user, simply use an HTTP client like Axios or Request to make a get request with the conversation id as a URL parameter to the endpoint we just created in the step above.

When the user responds to the next prompt, update the responded value to true so we can tell if the user responded from the proactive message.

async captureName(step) {
  const profile = await this.profileAccessor.get(step.context);
  profile.name = step.result;
  profile.responded = false;

  this.profileAccessor.set(step.context, profile);

  const { conversation: { id }} = TurnContext.getConversationReference(step.context.activity);

  setTimeout(() => {
    axios.get(`http://localhost:3978/api/notify/${id}`)
      .then(() => {})
      .catch(error => console.log(error));
  }, 60000);

  return await step.next();
}

async promptForCity(step) {
  return await step.prompt(CITY_PROMPT, "What city are your from?");
}

async captureCity(step) {
  const profile = await this.profileAccessor.get(step.context);
  profile.city = step.result;
  profile.responded = true;
  this.profileAccessor.set(step.context, profile);
  return await step.next();
}

Bot

In the proactive messaging sample, all of the conversation references are stored in an object. We can use the conversation id from the get request as the key value to retrieve the conversation reference and use the reference to continue the conversation. From the proactive message, you can send activities, access and update state, cancel dialogs, and all of the other normal functions you can do with a bot.

class Bot extends ActivityHandler{

    constructor(adapter, conversationState, userState) {
        super();
        this.adapter = adapter;
        this.conversationReferences = {};

        this.conversationState = conversationState;
        this.userState = userState;

        // Configure properties
        this.profileAccessor = this.userState.createProperty(USER_PROFILE);
        this.dialogState = this.conversationState.createProperty(DIALOG_STATE);


    }

    async sendProactiveMessages(conversationId) {

        const conversationReference = this.conversationReferences[conversationId];

        conversationReference && await this.adapter.continueConversation(conversationReference, async context => {
            const { responded } = await this.profileAccessor.get(context);
            if (!responded) {
                const dc = await this.dialogs.createContext(context);
                await dc.cancelAllDialogs();
                await context.sendActivity('Sorry you took too long to respond..');
                await this.conversationState.saveChanges(context);
            }
        });
    }
}

I know this a bit complicated for a simple action, but I hope this helps!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...