0

0

如何将异步生成器通过 MessageChannel 跨上下文透明代理

碧海醫心

碧海醫心

发布时间:2026-02-10 23:44:21

|

748人浏览过

|

来源于php中文网

原创

如何将异步生成器通过 MessageChannel 跨上下文透明代理

本文介绍一种简洁、原生的方案,使用 async function* 直接封装 messagechannel 通信,将远端异步生成器无缝暴露为本地可迭代的 asynciterator,无需手动实现复杂的状态机或中间迭代器。

在 Web Workers、Service Workers 或跨 iframe 场景中,常需将一个运行在隔离上下文(如 Worker)中的异步生成器(async function*)“透传”到主线程使用。传统做法(如自定义 remoteGenerator)需手动维护 Promise 状态、next/return 生命周期及错误传播,代码冗长且易出错。而借助现代 JavaScript 的 async generator 语法与 MessageChannel 的事件驱动特性,可实现更简洁、语义清晰、符合标准迭代协议的代理方案。

核心思路:利用 async generator 的自动暂停机制

关键洞察在于:*调用 `async function返回的生成器对象后,它立即处于“暂停”状态;首次调用.next()才真正启动执行,并阻塞在首个yield或return处。** 这意味着我们可在生成器函数体中——在首次yield前——主动向远端发送初始next()请求,随后等待响应;之后每次yield都自然对应一次port.postMessage(...)和await Promise` 的响应等待流程。

以下是重构后的核心代理函数:

/**
 * 将远端异步生成器通过 MessageChannel 代理为本地 async iterator
 * @param {MessagePort} port - 接收远端生成器控制消息的 MessagePort
 * @yields {any} 远端生成器产出的每个值
 * @returns {Promise} 远端生成器 return() 的最终值(若存在)
 */
async function* wrapRemoteGenerator(port) {
  try {
    // 启动远端生成器:发送首个 next(),无参数
    port.postMessage({ name: 'next', arg: undefined });

    // 主循环:持续接收响应、yield 值、发送后续 next()
    while (true) {
      // 等待远端响应(resolve 或 reject)
      const res = await new Promise((resolve, reject) => {
        const handlers = { resolve, reject };
        port.onmessage = (evt) => {
          // 根据消息中的 handler 字段分发至对应 Promise 处理器
          handlers[evt.data.handler]?.(evt.data);
        };
      });

      // 若远端返回 done: true,终止迭代并返回最终值
      if (res.done) {
        return res.value;
      }

      // 否则 yield 当前值,并等待下次 .next(arg) 调用传入的参数
      const arg = yield res.value;
      // 将用户传入的参数发给远端,继续迭代
      port.postMessage({ name: 'next', arg });
    }
  } finally {
    // 清理资源:无论正常结束或异常中断,都关闭端口
    port.close();
  }
}

使用示例:主线程与 Worker 协同

假设 Worker 中已定义并运行了一个异步计数器:

Bardeen AI
Bardeen AI

使用AI自动执行人工任务

下载
// worker.js
async function* startCounterAsync(delay = 1000) {
  let i = 0;
  while (i < 5) {
    yield i++;
    await new Promise(r => setTimeout(r, delay));
  }
}

// 在 Worker 内启动并监听 port 消息
self.onmessage = async function (e) {
  const { port } = e.data;
  const gen = startCounterAsync(500);

  port.onmessage = async (evt) => {
    try {
      const result = await gen[evt.data.name](evt.data.arg);
      port.postMessage({
        ...result,
        handler: 'resolve',
        done: result.done,
      });
    } catch (err) {
      port.postMessage({
        handler: 'reject',
        value: err,
        done: true,
      });
    }
  };
};

主线程中创建通道并消费代理生成器:

// main.js
const worker = new Worker('worker.js');
const channel = new MessageChannel();

worker.postMessage({ port: channel.port2 }, [channel.port2]);

// 获取代理后的 async iterator
const remoteIter = wrapRemoteGenerator(channel.port1);

// 直接 for-await-of 消费,行为与本地 async generator 完全一致
(async () => {
  try {
    for await (const value of remoteIter) {
      console.log('Received:', value); // 输出 0, 1, 2, 3, 4
    }
    console.log('Done.');
  } catch (err) {
    console.error('Iteration error:', err);
  }
})();

注意事项与最佳实践

  • 严格遵循迭代协议:该实现在 done: true 时正确 return 最终值,支持 for await...of、iter.next()、iter.return()(由 finally 保证端口关闭)等全部标准操作。
  • ⚠️ 错误传播:远端 throw 会触发 port.postMessage({ handler: 'reject', ... }),被 Promise.reject() 捕获,进而使 await 抛出错误,符合 AsyncIterator 错误语义。
  • ⚠️ 单次消费限制:wrapRemoteGenerator 返回的迭代器不可重复使用(同原生生成器),多次调用 Symbol.asyncIterator 会创建新实例。
  • 资源安全:finally 块确保 port.close() 总被执行,避免内存泄漏。
  • ? 不支持 iter.throw():本方案未实现对远端 throw() 的代理(因标准 AsyncIterator 并不要求必须支持)。如需,可扩展 port.postMessage({ name: 'throw', arg }) 分支并捕获远端 reject。

总结

相比手动实现 AsyncIterator 接口的复杂状态管理,async function* wrapRemoteGenerator(port) 提供了一种更轻量、更健壮、更易维护的跨上下文生成器代理方案。它充分利用了语言原生特性,将通信逻辑内聚于生成器函数体内,使调用方获得完全透明、符合直觉的异步迭代体验。在构建模块化、分布式 Web 应用时,这是连接隔离执行环境的理想桥梁。

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

382

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

242

2023.10.07

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1367

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

318

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2202

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

36

2026.01.19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

633

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

633

2023.08.10

包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法
包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法

本专题汇总了包子漫画官网和网页版入口,提供最新章节抢先看方法、正版免费阅读指南,以及稳定访问方式,帮助用户快速直达包子漫画页面,无广告畅享全集漫画内容。

50

2026.02.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号