无法在setTimeOut中获取对话状态属性值

时间:2019-04-23 17:01:49

标签: javascript azure botframework

我正在将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.

任何解决方案吗?

3 个答案:

答案 0 :(得分:4)

由于某种原因,BotFramework无法与回调一起很好地工作,这就是为什么您收到“无法对已撤销的代理执行'get'的操作”错误的原因。解决此问题的方法(但很复杂)是创建一个主动消息API端点,从超时回调中对其调用一个请求,然后从该主动消息中执行其余的机器人程序调用。我建议在开始下面的代码之前,先看一下Proactive Messaging示例。

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请求,并将会话ID作为URL参数。

当用户响应下一个提示时,将响应值更新为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);
            }
        });
    }
}

对于一个简单的操作,我知道这有点复杂,但是我希望这会有所帮助!

答案 1 :(得分:0)

使用Promise解决了此问题,我只是将timeOut移到了另一个函数中。

async partialNotify(step) {
    return new Promise(async resolve => {
      setTimeout(async () => {
      const notif = await this.reservationNotifProp.get(step.context);
      if (notif) {
        console.log("Send Notif ...");
      }
    }, 50000);
    resolve(true);
  });
}

答案 2 :(得分:-1)

请检查对话状态是否已在index.js中声明,如下所示。

const conversationState = new ConversationState(memmoryStorage);