指令的生命周期
会话的概念
为了实现高级的指令功能,我们引入了会话的概念
什么叫会话呢?从用户触发指令开始,到指令结束之间的完整过程,就是一个会话了
在指令会话的不同阶段,我们抽象出了一系列的“钩子”,在会话执行到该阶段时,自动触发,称之为“生命周期”
生命周期的意义
如果你用过 Vue,对 PepperBot 的生命周期一定很眼熟,事实上,PepperBot 的生命周期,就是学习自 Vue
比如 Vue 中的created, mounted,在 PepperBot 中对应的就是 initial
destroyed对应 PepperBot 中的 finish, exit
得益于生命周期,我们可以轻松定义出灵活、高可读性的指令
完整的生命周期及参数,[见此]
对比try except else finally
其实,catch、finish、cleanup基本上效果和python自带的except、else 、finally是一样的
之所以名字并未直接命名为except、else 、finally,是因为except、else 、finally是python的关键字,如果用这些作为生命周期的名称,会导致语法错误
假设有这样一段异常捕获
try:
    ...
except Exception as e:
    ...
else:
    ...
finally:
    ...
等价的生命周期写法
class MyCommand:
    async def initial(self): # 对应try,任意非catch、finish、cleanup的方法
        ...
    async def catch(self): # 对应except
        ...
    async def finish(self): # 对应else
        ...
    async def cleanup(self): # 对应finally
        ...
或者更直观一点
try:
    ...
except CommandTimeout:
    try:
        await MyCommand.timeout()
    except Exception as e:
        await MyCommand.catch(e)
except CommandExit:
    try:
        await MyCommand.exit()
    except Exception as e:
        await MyCommand.catch(e)
except Exception as e:
    await MyCommand.catch(e)
else:
    try:
        await MyCommand.finish()
    except Exception as e:
        await MyCommand.catch(e)
finally:
    await MyCommand.cleanup()
如果完全按照python的try、except、else、finally的语法,else里的finish,是不会捕获的
不过为了方便开发者,对于finish,我们也会捕获异常
总结一下,就是catch,会捕获除了catch生命周期自身 + cleanup生命周期以外的所有异常,包含timeout、exit、finish
入口initial
什么是入口?就是当用户触发指令时,我们最先执行的方法,即为入口
在 PepperBot 中,入口函数的名称,要求是initial
比如我们有一条触发关键词为"今日头条"的指令
那么,当用户发送“今日头条”时,就会自动触发入口initial钩子
class 今日头条:
    async def initial(self):
        # 当触发指令时,首先执行initial函数
        ...
指令超时timeout
我们在声明指令的as_command装饰器中,可以设置timeout参数,当用户超过指定的时长未回复机器人时,会自动触发该钩子
class MyCommand:
    async def timeout(self):
        # 当超时时,触发该钩子
        ...
对比异常捕获的话,可以理解为
try:
    ...
except CommandTimeout:
    await MyCommand.timeout()
用户主动退出exit
我们在声明指令的as_command装饰器中,可以设置exit参数,exit为一个列表,其中每一个元素,都应是一个正则表达式,当用户输入的纯文字内容(即chain.pure_text)满足任意一个正则时,触发exit钩子
class MyCommand:
    async def exit(self):
        # 当用户主动退出时,触发该钩子
        ...
对比异常捕获的话,可以理解为
try:
    ...
except CommandExit:
    await MyCommand.exit()
异常捕获catch
在执行指令的任意handler(除catch以外的任意生命周期,或者所有用户定义的方法)时,如果触发异常,如果用户定义了catch,那么则会执行该钩子,有点像是 react 的error boundary
在执行catch生命周期后,根据catch函数的返回指向,可以继续执行指令,也可以直接结束指令,具体见声明指令
class MyCommand:
    async def catch(self):
        # 当其他handler执行出错时,触发该钩子
        ...
对比异常捕获的话,可以理解为
try:
    ...
except CommandTimeout:
    try:
        await MyCommand.timeout()
    except Exception as e:
        await MyCommand.catch(e)
except CommandExit:
    try:
        await MyCommand.exit()
    except Exception as e:
        await MyCommand.catch(e)
except Exception as e:
    await MyCommand.catch(e)
指令自然终止finish
如果我们的指令,在执行过程中,用户响应没有超时,用户也没有主动退出,指令执行也没有出现任何异常,那么,就会触发指令的自然终止钩子
class MyCommand:
    async def finish(self):
        # 当指令自然退出时,触发该钩子
        ...
对比异常捕获的话,可以理解为
try:
    ...
else:
    await MyCommand.finish()
指令清理cleanup
类似于finally,指令执行完毕后,会自动触发cleanup钩子
class MyCommand:
    async def cleanup(self):
        # 当指令执行完毕后,触发该钩子
        ...
对比异常捕获的话,可以理解为
try:
    ...
finally:
    await MyCommand.cleanup()
复用生命周期的定义
PepperBot 的指令,是基于class的,所以,我们可以基于 mixin,实现生命周期的复用
比如,我们定义了比较通用的生命周期mixin
class CommonMixin:
    # 用户主动退出
    async def exit(self):
        ...
    # 流程正常退出(在中间的流程return None也是正常退出)
    async def finish(self):
        ...
    async def timeout(self):
        ...
现在,当我们定义不同的指令时,就可以复用生命周期了
class 指令1(CommonMixin):
    ...
class 指令2(CommonMixin):
    ...
当指令混入 mixin 后,已经定义的方法是可以覆盖的
比如,我们想在指令1中,自定义一下exit钩子,那么,我们只需要
class 指令1(CommonMixin):
    async def exit(self):
        ...
这样,我们就按照我们的需要,重新定义了exit钩子,其它两个未定义的钩子finish, timeout, 还是保留未被修改的状态,也就是说,“合并了”