使用promise创建一个同步事件驱动api

news2024/10/7 10:24:14

使用promise创建一个同步事件驱动api

事件驱动体系结构(EDA)是一种强大的方法,可以在网络上构建松散耦合、性能好、可伸缩的应用程序。它支持非常多功能,如推送通知、协作编辑和多人操作,以及其他实时交互。

但有时模型与我们开发人员需要的不一致。当两个应用程序层只能通过异步消息传递进行通信时,我们就不得不重新修改代码。

在本文中,将使用Promise实现一个通用的事件驱动API,它隐藏了消息传递的复杂性和模板,并允许我们跨应用程序编写线性代码。

Request/Response vs EDA

传统的网络应用程序处理跨边界的通信通常都是使用HTTP这种Request/Response模式。该模型的特点是请求者发送消息,然后等待响应,接收、处理和响应消息。尽管这可能发生在异步,但是我们可以称此模型为"同步"。

在这里插入图片描述
EDA中也被称为 发行/订阅模式,请求和接收数据的过程是独立的,以非阻塞、异步的方式进行。通常,客户端会订阅来自服务器的消息,服务器会订阅来自客户端的消息。当客户端请求数据时,它只是发出信息,然后继续执行。服务器接收消息,处理它,并在某个时候向客户端发送另一条消息。客户端作为订阅者,从其原始请求中接收消息并处理它认为合适的消息。但是使用不同的网络请求,甚至其他协议,这可能会在另一个时间发生。

在这里插入图片描述

事件驱动模型此时就出现了一些关键优势。首先,EDA允许客户端在服务器上的事件响应时进行通知。这消除了昂贵的轮询,并支持对来自别处的通知和事件进行推送。其次,它可以通过允许消息处理与消息发送分离来鼓励较少耦合的代码。第三,它赋予开发人员并行化处理和构建弹性系统的能力。第四,它使系统具有固有的容错性,因为只有被识别的消息才会被订阅者处理。

EDA的劣势

在复杂的应用程序中,事件驱动的架构可以提供大量的优势。但有时候我们只需要在特定的执行上下文中立即获取数据。或者我们只是想把远程资源当作本地资源来处理。

假设我们有一个应用程序,它必须对用户输入执行一些复杂的计算。我们最好使用web worker,它使用一个单独的线程进行工作。

// Create a Web Worker
const worker = new Worker("worker.js");

const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
  worker.postMessage({ type: "expensiveComputation", payload: 42 });
});

worker.addEventListener("message", (event) => {
  const { type, payload } = event.data;
  switch(type) {
    case "expensiveComputation":
      doSomethingWithResult(payload);
      break;
      
    default:
      break;
  }
});

我们的work.js模块监听来自主线程的消息,执行昂贵的计算,并向主线程发送响应:

self.onmessage = async (event) => {
  const { type, payload } = event.data;
  switch(type) {
    case "expensiveComputation":
      const result = await doExpensiveComputation(payload);
      postMessage({ type, payload: result });
      break;

    default:
      break;
  }
}

客户端可以发送计算请求,然后收到结果,执行doSomethingWithResult方法。但这个解决方案有个限制,我们不能在同一个地方请求数据并使用响应的数据 。

实现一个Sync Bridge

为了以"同步"的方式消耗事件流,我们需要将其接口转换成一个使用Promise的接口–Sync Bridge

  1. 在客户端发送消息之前,给消息带上id 或者是唯一标识,用于匹对响应消息。
  2. 初始化一个Promise
  3. 把这个Promise 返回给请求者,发送消息。
  4. 在主机订阅客户端消息。
  5. 订阅在客户端上的主机消息。当收到一条载有id消息时,检查是否是pending状态。如果是,那么从数据结构中弹出它,并决定或拒绝承诺。这里的"客户端"和"主机"可以是参与消息传递的任何实体。有时客户端也可以充当主机,反之亦然,因此我们也可以根据这些实体的使用上下文将其称为"请求者"和"响应者"。

通过在客户端和主机之间创建一个协议,同意用一个共同的、唯一的ID标记请求和相应的响应消息,使响应能够"路由"到正确的请求者,我们就可以规避EDA的限制。我们在请求端留下一个作为占位符的Promise,一旦收到等待的消息,我们就用真实数据来填充。

要将此应用于我们之前的web worker代码,让我们编写一些助手类来抽象上面列出的流程。我们需要某种客户端抽象来为消息分配ID,跟踪挂起的请求,并监听响应:

const DEFAULT_CHANNEL = "__worker_channel";

export class WorkerClient {
  #channel;
  #worker;
  #receiver;
  #pending = new Map();

  constructor(workerUrl, channel = DEFAULT_CHANNEL) {

    this.#channel = channel;
    this.#worker = new Worker(workerUrl, { type: "module" });
    this.#receiver = (event) => {
      if (!event.data || !event.data.id) return;
      if (event.data.channel !== this.#channel) return;
      if (!this.#pending.has(event.data.id)) return;
      const [resolve, reject] = this.#pending.get(event.data.id);
      if ("payload" in event.data) {
        resolve(event.data.payload);
      } else if ("error" in event.data) {
        reject(event.data.error);
      } else {
        reject(new Error("Malformed response"));
      }
      this.#pending.delete(event.data.id);
    };

    this.#worker.addEventListener("message", this.#receiver);
  }

  async post(payload) {
    const id = Math.floor(Math.random() * 1_000_000).toString(16);
    return new Promise((resolve, reject) => {
      this.#pending.set(id, [resolve, reject]);
      
      // Dispatch a message to the worker
      this.#worker.postMessage({
        id,
        channel: this.#channel,
        payload,
      });
    });
  }
}

在主机方面,我们需要一个控制器,它过滤掉我们不感兴趣的消息,执行一些单元的工作,并将消息发送到请求者的"返回地址":

 export class WorkerHost {
  #channel;
  #receivers = new Map();

  constructor(channel = DEFAULT_CHANNEL) {
    this.#channel = channel;
  }

  on(type = "message", callback) {
    const wrapper = async (event) => {
      // Filter out irrelevant messages
      if (!event.data || !event.data.id) return;
      if (event.data.channel !== this.#channel) return;
     
      try {
        const payload = await callback(event);
        postMessage({
          id: event.data.id,
          channel: this.#channel,
          payload,
        });
      } catch (error) {
        postMessage({
          id: event.data.id,
          channel: this.#channel,
          error,
        });
      } 
    };

    this.#receivers.set(callback, wrapper);
    addEventListener(type, wrapper);
  }

  off(type = "message", callback) {
    const wrapper = this.#receivers.get(callback);
    if (wrapper) {
      removeEventListener(type, wrapper);
      this.#receivers.delete(callback);
    }
  }
}

使用这些辅助程序,我们将改写之前应用程序代码,以使用基于Promise的新·API·。注意我们现在可以直接在我们的点击处理程序中的处理数据:

const client = new WorkerClient("worker.js");

const btn = document.getElementById("btn");
btn.addEventListener("click", async () => {
  const data = await client.post({ type: "expensiveComputation", payload: 42 });
  doSomethingWithResult(data);
});

我们的处理程序现在只是返回值,而不是发布消息(消息传递是为我们处理的):

const host = new WorkerHost();

host.on("message", async (event) => {
  const { type, payload } = event.data;
  switch(type) {
    case "expensiveComputation":
      const result = await doExpensiveComputation(payload);
      return result;

    default:
      break;
  }
});

我们已经写了相当数量的代码。我们到底得到了什么?

Sync Bridge适配器作为一个真正的Promise,实质上改变了预期将来会收到一些信息。它允许我们在远程上下文中处理数据和代码,就像它是本地的一样。最重要的是,它允许我们在同一地点请求和使用远程数据。

我们现在还可以将不同类型的消息限制在离散通道上,使消息处理特定、快速和本地化的代码只限于需要的地方。如果我们想的话,多重的WorkerClient甚至可以共享同一个通道。

这种模式可以很容易地推广到大多数事件驱动的系统。我们可以修改我们示例中的方法,以接收更多EventTarget ,为任何消息流提供通用的同步接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1191269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业镜头接口类型

现有产品主要有以下接口 1、C:最常见的工业相机接口,受限于接口物理尺寸大小,最大靶面目前是4/3” 2、M42:M421.0,2k和4k线阵相机使用 3、M58S:M580.75,大靶面相机使用,可以转C(限于CH080相机,靶面4/3”),可以转F,可以…

Selenium关于内容信息的获取读取

在进行自然语言处理、文本分类聚类、推荐系统、舆情分析等研究中,通常需要使用新浪微博的数据作为语料,这篇文章主要介绍如果使用Python和Selenium爬取自定义新浪微博语料。因为网上完整的语料比较少,而使用Selenium方法有点简单、速度也比较慢,但方法可行,同时能够输入验…

中国第二批,11个大模型备案获批

加上首批的 10 余个大模型,目前已有超过 20 个大模型获得审批。 据钛媒体独家报道,国内第二批通过备案的AI大模型包括11家公司,部分已面向全社会开放服务。加上首批的10余个大模型,目前已有超过20个大模型获得备案。 新一批备案…

Python数据容器(列表)

目录 一.什么是数据容器二.数据容器:列表1.列表的定义2.列表的下标索引3.列表的常用操作4.总结5.练习 三.列表的遍历1.列表的遍历2.while循环和for循环的对比3.练习 一.什么是数据容器 1.什么是数据容器 一种可以存储多个元素的Python数据类型 2.Python有哪些数…

c语言总是有小问题,是练的少吗?

c语言总是有小问题,是练的少吗? 题主说我做c语言的题目时候,是有思路的并且可以按照想法写下来,大体上看没有问题,但是到运行的时候总是不过关。就需要很长的时间找出那个细微的错误,这种细微的错误怎么才能…

“深入理解机器学习性能评估指标:TP、TN、FP、FN、精确率、召回率、准确率、F1-score和mAP”

目录 引言 分类标准 示例:癌症检测 1. 精确率(Precision) 2. 召回率(Recall) 3. 准确率(Accuracy) 4. F1-score 5. mAP(均值平均精度) 总结与通俗解释 引言 机器…

自动驾驶算法(九):多项式轨迹与Minimun Snap原理与Matab代码详解

目录 1 为什么需要轨迹优化 2 代码解析 3 完整代码 1 为什么需要轨迹优化 我们利用前八篇所学的博客可以利用RRT、A*、遗传算法等设计出一条折线轨迹,轨迹优化就是在路径优化的基础上将折线优化成曲线,这样更加有利于无人机的飞行。 那么什么是多项式轨…

史上第一款AOSP开发的IDE (支持Java/Kotlin/C++/Jni/Native/Shell/Python)

ASFP Study 史上第一款AOSP开发的IDE (支持Java/Kotlin/C/Jni/Native/Shell/Python) 类似于Android Studio,可用于开发Android系统源码。 Android studio for platform,简称asfp(爱上富婆)。 背景&下载&使用 背景 由…

8.4 矢量图层点要素分类(Categorized)渲染使用

文章目录 前言分类(Categorized)渲染QGis代码实现 总结 前言 前面几章介绍了矢量-点要素-单一符号的各种用法所谓单一符号是指点要素的符号在图层显示时只有一种形式下面介绍的分类(Categorized)渲染说明:文章中的示例…

【Python爬虫库】pytube使用方法

一、pytube库简介 pytube库是一个python第三方库,用于youtube视频的抓取和其他相关操作。官方文档:pytube 二、基本操作 1、显示视频标题 from pytube import YouTube yt YouTube(https://youtube.com/watch?vIAJsZWhj6GI) print(yt.title)说明&am…

自建网盘平台搭建(源码+教程)

为什么要自己搭建网盘,现在许多大厂的网盘,文件都添加了许多限制,有好多文件会遭到和谐,而且大部分网盘也都会限速,不开通VIP是很难用的!这是一套可以运营的网盘,代码无加密可以进行二次开发。下…

dos命令bat结合任务计划程序自动执行py文件

效果 bat文件 run.bat @echo off call C:\ProgramData\Anaconda3\Scripts\activate.bat pytorch C:\ProgramData\Anaconda3\envs\pytorch\python.exe E:\Gerapy_py\gerapy\projects\xmtv\xmtv\start_urls.py下面这个bat文件可以用来判断py文件是否执行成功 @echo off call C…

【Java】Netty创建网络服务端客户端(TCP/UDP)

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Netty创建网络服务端客户端示例。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更…

MySQL模糊查询/模式匹配(Pattern Match)

使用SQL查询数据时,时常会遇到这种情况,我们并不需要精确的匹配,而是要查找具有某类特点的数据。这种场景我们就要用到模糊查询。MySQL中常用的模糊查询方法有2种: like语句模糊查询regexp正则表达式模式匹配 目录 一、使用like模…

基于SSM的社区生鲜电商平台

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

Java / Android 多线程和 synchroized 锁

s AsyncTask 在Android R中标注了废弃 synchronized 同步 Thread: thread.start() public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionali…

Visual Interpretability for Deep Learning: a Survey

Visual Interpretability for Deep Learning: a Survey----《深度学习的视觉可解释性:综述》 摘要 本文回顾了最近在理解神经网络表示以及学习具有可解释性/解耦的中间层表示的神经网络方面的研究。尽管深度神经网络在各种任务中表现出了优越的性能,但可解释性始终…

基于SpringBoot+Vue+uniapp微信小程序实验室预约管理平台详细设计和实现

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

getid3 获取视频时长

1、首先,我们需要先下载一份PHP类—getid3https://codeload.github.com/JamesHeinrich/getID3/zip/master 2.我在laravel6.0 中使用 需要在composer.json 自动加载 否则系统访问不到 在命令行 执行 composer dump-autoload $getID3 new \getID3();//视频文件需要放…

【PostgreSql本地备份为dump文件与恢复】使用脚本一键备份为dump文件

环境:windows数据库:postgresql 1.准备脚本 backUpDb.bat 脚本为备份脚本,双击运行,右键可以选择编辑;restoreDb.bat 脚本为恢复脚本,双击运行,右键选择编辑; 1.1 脚本介绍 如上图…