无法在setTimeOut中获取对话状态属性值 [英] Cannot get conversationState property value inside a setTimeOut

查看:51
本文介绍了无法在setTimeOut中获取对话状态属性值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将botBuilder SDK 4.3用于Node js.

我在对话框的构造函数中创建了一个sessionState属性. 在某些对话框步骤中,我为该属性设置了一个值.

在另一步骤中,我试图在setTimeOut之内获取该属性的值,如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);
  }
}

超时结束后,出现此错误,并且notifundefined:

(node:47504) UnhandledPromiseRejectionWarning: TypeError: Cannot perform 'get' on a proxy that has been revoked
    at ConversationState.load (c:\Users\Montacer\Desktop\qt-bot\node_modules\botbuilder\node_modules\botbuilder-core\src\botState.ts:84:48)
    at BotStatePropertyAccessor.get (c:\Users\Montacer\Desktop\qt-bot\node_modules\botbuilder\node_modules\botbuilder-core\src\botStatePropertyAccessor.ts:97:43)
    at Timeout.setTimeout [as _onTimeout] (c:\Users\Montacer\Desktop\qt-bot\dialogs\reservation.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.

任何解决方案吗?

解决方案

由于某种原因,BotFramework无法与回调配合使用,这就是为什么您会收到无法对已撤销的代理执行'get'操作"的原因错误.解决此问题的方法(但很复杂)是创建一个主动消息API端点,从超时回调中对其调用请求,然后从该主动消息中执行其余的漫游器调用.我建议您看一下主动消息传递开始使用下面的代码之前的示例.

index.js文件

我们将向我们的Restify服务器添加一个/api/notify终结点,该终结点将在超时结束时被点击.我建议向您的机器人添加一种方法来处理发送主动消息,这样您就可以保留机器人中包含的所有状态和对话框元素,而不必将其提升为索引文件.请注意,您必须将适配器作为参数传递给您的机器人.

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

...

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

对话框

在对话框的此步骤中,我们正在将响应的属性添加到用户个人资料-您也可以将其添加到对话状态-并将默认值设置为false.然后,无需配置回调来访问状态并向用户发送消息,只需使用HTTP客户端(如Axios或Request)向会话代码作为我们在上一步中创建的端点的URL参数发出get请求即可.

当用户响应下一个提示时,将响应值更新为true,这样我们就可以判断用户是否从主动消息中响应了.

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();
}

启动

在主动消息传递示例中,所有对话引用都存储在一个对象中.我们可以使用get请求中的对话ID作为键值来检索对话引用,并使用该引用继续对话.通过主动消息,您可以发送活动,访问和更新状态,取消对话框以及您可以使用漫游器执行的所有其他正常功能.

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 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:\Users\Montacer\Desktop\qt-bot\node_modules\botbuilder\node_modules\botbuilder-core\src\botState.ts:84:48)
    at BotStatePropertyAccessor.get (c:\Users\Montacer\Desktop\qt-bot\node_modules\botbuilder\node_modules\botbuilder-core\src\botStatePropertyAccessor.ts:97:43)
    at Timeout.setTimeout [as _onTimeout] (c:\Users\Montacer\Desktop\qt-bot\dialogs\reservation.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 ?

解决方案

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!

这篇关于无法在setTimeOut中获取对话状态属性值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆