协程-面向思想的编程

120 阅读2分钟

一.问题描述

协程的本质其实就是:以任务为单位对线程进行切片。下面是一个具体项目中协程的应用示例,请看下面的应用场景图:

以上应用场景为微信中的消息转发场景。在Message模块选择一条消息后,去到Contact模块选择联系人后,弹出确认弹窗,等待用户输入一些其他文本内容后确认即可,构造对应的转发消息通过IM通道发送给选择了的联系人对象。以下为项目初期的实现代码:

transferPicker(merge: boolean, transferData: IMChatMessage[]) {
  const pickerParams: PickerContactBaseParam = {
    finishedPicker: (pickedModels: PickedBaseModel[], _?: string | object | null) => {
      MLog.D(TAG, 'transferPicker', pickedModels)
      this.dialogController = new CustomDialogController({
        builder: ShareContentDialog({
          pickedModels: pickedModels,
          contentData: { content_text: contentText },
          onConfirm: async (contentData: ShareContentData, shareText?: string) => {
            MLog.D(TAG, 'transferPicker onConfirm', pickedModels)
            for (let i = 0; i < pickedModels.length; i++) {
              const target = pickedModels[i]
              let session: ISession | undefined = undefined
              if (target instanceof PickedUserModel) {
                session = await MsgSendUtils.getOrCreateSession(target.userId, target.appKey, false)
              } else if (target instanceof PickedGroupModel) {
                session = await MsgSendUtils.getOrCreateSession(target.groupId, target.appKey, true)
              }
              if (session) {
                const selectMsgs = MsgUtils.toChatMsgsId(transferData)
                if (merge) {
                  msgSend.sendMergeMsg(session.sid, selectMsgs)
                } else {
                  for (let i = 0; i < selectMsgs.length; i++) {
                    msgSend.sendForwardMsg(session.sid, selectMsgs[i])
                  }
                }
                if (shareText) {
                  msgSend.sendMsg(session.sid, shareText)
                }
              }
            }
            msgSelectUtils.clean()
            this.checkBoxVisible = false
            MNavRouteHelper.popToName('ConversationDetailPage')
          }
        }),
        alignment: DialogAlignment.Center,
        width: '85%',
        customStyle: true,
      })
      this.dialogController.open()
    },
    entranceType: EnterContactActionType.IM_MESSAGE_TYPE,
  }
  MNavRouteHelper.push(ContactPickerInterFace.HomeRouter, pickerParams)
}

代码分析:

1.以匿名函数的方式接收从Contact模块选择的联系人/群后;

2.将选择了的人/群作为参数弹出确认弹窗

3.在弹窗的匿名函数中接收用户确认操作后,遍历选择了的联系人数组后,通过IM通道将消息转发出去

存在的问题:

1.选择人/群逻辑可以封装

2.弹窗实现细节也可以封装

3.最大的问题还是匿名函数嵌套匿名函数,加深代码复杂度

二.解决方案

采用协程的方案替换以上的匿名函数嵌套问题:


public async transferMsg(msgText: string) {
  //选人
  const pickedModels = await ContactPickerInterFace.pickContactFromPickHome(EnterContactActionType.IM_MESSAGE_TYPE)
  
  //确认弹窗
  const shareText = await ShareDiagPromptUtil.showDialog(pickedModels, {
    content_text: msgText,
  })
  
  //转发消息
  ShareImMsgUtil.shareTextMsg(pickedModels, msgText)
  if (shareText) {
    ShareImMsgUtil.shareTextMsg(pickedModels, shareText)
  }
  
  //退出选择器
  MNavRouteHelper.popToPreviousByName(ContactPickerInterFace.HomeRouter)
}

以上代码采用协程的方案将:1.选人;2.确认弹窗;3.转发IM消息。三个步骤封装解耦合的非常简洁,清晰,明了。

选人任务以协程形式封装示例:

static async pickContactFromPickHome(entranceType:EnterContactActionType):Promise<PickedBaseModel[]> {
  return new Promise((resolve: Function, reject: Function) => {
    const pickerParams: PickerContactBaseParam = {
      finishedPicker: (pickedModels: PickedBaseModel[], extra?: object | string | null) => {
        //选择结束
        resolve(pickedModels)
      },
      entranceType: entranceType,
    }
    MNavRouteHelper.push(ContactPickerInterFace.HomeRouter, pickerParams)
  })
}

弹窗任务以协程形式封装示例:

async showDialog(pickedModels: PickedBaseModel[], contentData: ShareContentData): Promise<string | undefined> {
  const uiContext: UIContext = WindowUtil.windowStage.getMainWindowSync().getUIContext()
  this.promptAction = uiContext.getPromptAction();
  return new Promise(async (resolve, reject) => {
    const options: ShareDialogPromptParam = {
      pickedModels: pickedModels,
      contentData: contentData,
      onConfirm: (shareText?: string) => {
        this.closeCustomDialog()
        resolve(shareText)
      },
      cancelCallBack: () => {
        this.closeCustomDialog()
      }
    }
    this.contentNode = new ComponentContent(uiContext, wrapBuilder(customDialogBuilder), options);
    //创建弹框
    if (this.promptAction) {
      this.promptAction.openCustomDialog(
        this.contentNode,
        {
          showInSubWindow: false,
          offset: { dx: 0, dy: 0 },
          alignment: DialogAlignment.Center,
          maskRect: {
            x: 0,
            y: 0,
            width: '100%',
            height: '100%'
          },
          isModal: true
        })
    }
  })
}