Loading

消灭事件回调,让其直接变成线性同步的代码风格

在 C# 和 Javascript 语言下,讨论如何封装事件返回的回调

问题场景

比如有一个库中,有一个 send 方法,用于发送命令,然后需要等待返回值,但 send 方法本身没有返回值,而是通过另外的事件来获取返回值。
伪代码如下:

// 通过事件回调来接收命令执行结果
foo.onDataReceive = (result) => { // receive result }
// 发送命令
foo.send("command")

这在使用上其实不是很方便,而且理解起来不直观,期望可以有如下的封装

var result = await myFoo.Send("command")

下面介绍在 C# 和 Javascript 中如何处理,在 C# 中使用的是 TaskCompletionSource 这个 API,Javascript 中使用的就是 Promise

尤其是 C# 中的这个 API,其实很简单,但是如果不知道,还真一时半会想不到特别优雅的方案。
在 Javascript 中,Promise 的提出,作用之一就是为了解决回调地狱,所以这个方案在 Javascript 显得就很自然。

csharp 版本

MessageSender 是原始 API, MyMessageSender 是封装。这里就可以直接使用 SendAsync 进行异步调用拿到结果,或者捕获异常。

class MyMessageSender
{
    private TaskCompletionSource<string> _waitMessageSource = new();

    private readonly MessageSender _messageSender;

    public MyMessageSender()
    {
        _messageSender = new MessageSender();
        _messageSender.MessageReceived += (sender, args) =>
        {
            if (args.ErrorCode == 0)
            {
                // 成功收到数据,则设置数据
                _waitMessageSource.TrySetResult(args.Response);
            }
            else
            {
                // 没有成功,则抛出异常
                _waitMessageSource.TrySetException(new MessageReceivedException(args.ErrorCode, args.Response));
            }
        };
    }

    /// <summary>
    /// 发送请求数据,并获取响应
    /// </summary>
    /// <param name="request"></param>
    /// <returns>响应数据</returns>
    /// <exception cref="MessageReceivedException">数据接收出现错误</exception>
    public async Task<string> SendAsync(string request)
    {
        _waitMessageSource = new();
        _messageSender.Send(request);
        return await _waitMessageSource.Task;
    }

}

class MessageSender
{
    public event EventHandler<MessageReceivedEventArgs> MessageReceived;

    public void Send(string message)
    {

    }
}

class MessageReceivedEventArgs : EventArgs
{
    public int ErrorCode { get; set; }

    public string Response { get; set; } = "";
}

class MessageReceivedException(int code, string? message) : ApplicationException(message)
{
    public int ErrorCode { get; set; } = code;
}

javascript 版本

js 中直接使用 Promise 来包装回调,这个是很自然的操作

sender = {
  send(request, callback) {},
};

mySender = {
  send(request) {
    return new Promise((resolve, reject) => {
      let callback = (response) => {
        if (response.code == 0) {
          resolve(response.message);
        } else {
          reject({
            errorCode: response.code,
            message: response.message,
          });
        }
      };
      sender.send(request, callback);
    });
  },
};

好处

当然是让代码逻辑更清晰,将回调写法,变成线性执行,对于复杂业务来说,能够很好让代码更可读和可理解

posted @ 2024-06-16 20:28  J.晒太阳的猫  阅读(48)  评论(0编辑  收藏  举报