消灭事件回调,让其直接变成线性同步的代码风格
在 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);
    });
  },
};
好处
当然是让代码逻辑更清晰,将回调写法,变成线性执行,对于复杂业务来说,能够很好让代码更可读和可理解
作者:
J.晒太阳的猫
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。 

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号