You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中多回调/流异步迭代器的类型错误解决与替代方案

解决TypeScript中XHR异步迭代器的类型问题与替代方案

一、修复两类类型错误

1. 解决AsyncIterator缺少[Symbol.asyncIterator]的问题

for await...of要求遍历的是异步可迭代对象(AsyncIterable),而非单纯的AsyncIterator。EventIterator实例本身已实现AsyncIterable接口,问题出在类型注解的错误:

  • 先定义XHR所有可能的事件联合类型:
type XhrEvent =
  | { type: 'progress', loaded: number, total: number }
  | { type: 'load', response: string }
  | { type: 'error', error: Error };
  • 给callXhr指定返回类型为AsyncIterable<XhrEvent>,而非AsyncIterator<XhrEvent>:
import EventIterator from 'event-iterator';

function callXhr(request: RequestInit): AsyncIterable<XhrEvent> {
  return new EventIterator(queue => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', request.url || '', true);
    
    xhr.addEventListener('progress', e => {
      queue.push({ type: 'progress', loaded: e.loaded, total: e.total });
    });
    
    xhr.addEventListener('load', () => {
      queue.push({ type: 'load', response: xhr.responseText });
      queue.stop(); // 终止迭代
    });
    
    xhr.addEventListener('error', () => {
      queue.push({ type: 'error', error: new Error('XHR请求失败') });
      queue.stop();
    });
    
    xhr.send();
    
    return () => xhr.abort(); // 迭代终止时的清理逻辑
  });
}

修改后for await...of即可正确识别该对象为可迭代类型,消除报错。

2. 解决switch中data.type为never的问题

该错误是因为TypeScript无法自动推断data的联合类型。只要确保queue.push的所有值都严格匹配之前定义的XhrEvent联合类型,TypeScript就能在switch分支中正确缩小类型范围:

async function handleRequest(request: RequestInit) {
  for await (const data of callXhr(request)) {
    switch (data.type) {
      case 'progress':
        console.log(`进度:${data.loaded}/${data.total}`);
        break;
      case 'load':
        console.log(`响应内容:${data.response}`);
        break;
      case 'error':
        console.error(`错误信息:${data.error.message}`);
        break;
    }
  }
}

若仍有推断问题,可手动给data添加类型注解:const data: XhrEvent = ...,但通常联合类型定义正确时无需此操作。

二、tsconfig.json跨浏览器兼容配置

要让TypeScript识别Symbol.asyncIterator和XHR相关类型,需调整tsconfig.json的核心字段:

{
  "compilerOptions": {
    "target": "ES5", // 兼容IE11等旧浏览器
    "lib": ["ES2018", "DOM"], // ES2018包含异步迭代器类型,DOM包含XHR类型定义
    "downlevelIteration": true, // 为低版本ES编译异步迭代器的兼容代码
    "module": "ESNext",
    "moduleResolution": "Node"
  }
}
  • lib字段:必须包含ES2018以引入异步迭代器的类型,DOM确保XHR相关API的类型可用。
  • downlevelIteration:当target低于ES2018时启用,让TypeScript编译出兼容旧环境的异步迭代器代码。
  • 若无需兼容极旧浏览器,可将target设为ES2018或更高,此时downlevelIteration可省略。

注意:旧浏览器(如IE11)本身不支持Symbol.asyncIterator和for await...of,需引入core-js等工具的polyfill(如es.symbol.async-iterator)才能正常运行。

三、不依赖第三方库的简化方案

手动实现轻量的异步可迭代对象,直接封装XHR事件逻辑,无需依赖EventIterator:

type XhrEvent =
  | { type: 'progress', loaded: number, total: number }
  | { type: 'load', response: string }
  | { type: 'error', error: Error };

function createXhrIterable(request: RequestInit): AsyncIterable<XhrEvent> {
  return {
    [Symbol.asyncIterator]() {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', request.url || '', true);
      
      const eventQueue: XhrEvent[] = [];
      let isDone = false;
      let resolveNext: ((value: IteratorResult<XhrEvent>) => void) | null = null;
      
      const pushEvent = (event: XhrEvent) => {
        eventQueue.push(event);
        resolveNext?.({ value: event, done: false });
        resolveNext = null;
      };
      
      xhr.addEventListener('progress', e => pushEvent({ type: 'progress', loaded: e.loaded, total: e.total }));
      xhr.addEventListener('load', () => {
        pushEvent({ type: 'load', response: xhr.responseText });
        isDone = true;
        resolveNext?.({ value: undefined, done: true });
      });
      xhr.addEventListener('error', () => {
        pushEvent({ type: 'error', error: new Error('XHR请求失败') });
        isDone = true;
        resolveNext?.({ value: undefined, done: true });
      });
      
      xhr.send();
      
      return {
        next(): Promise<IteratorResult<XhrEvent>> {
          if (eventQueue.length > 0) {
            return Promise.resolve({ value: eventQueue.shift()!, done: false });
          }
          if (isDone) {
            return Promise.resolve({ value: undefined, done: true });
          }
          // 等待下一个事件触发
          return new Promise(resolve => {
            resolveNext = resolve;
          });
        },
        // 可选:实现return方法,支持迭代中途取消XHR
        return(): Promise<IteratorResult<XhrEvent>> {
          xhr.abort();
          isDone = true;
          return Promise.resolve({ value: undefined, done: true });
        }
      };
    }
  };
}

// 使用示例
async function runRequest() {
  const iterable = createXhrIterable({ url: '/api/data' });
  for await (const data of iterable) {
    switch (data.type) {
      case 'progress':
        console.log(`进度:${Math.round((data.loaded/data.total)*100)}%`);
        break;
      case 'load':
        console.log('请求完成:', data.response);
        break;
      case 'error':
        console.error('请求失败:', data.error);
        break;
    }
  }
}

该方案完全手动控制异步迭代流程,同时保持类型安全,无需引入第三方依赖。

内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 06:24:55