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

Redux Observable:多PLAYER_SCRIPT_LOAD动作仅触发一次异步回调问题

问题分析与解决方案

你的核心问题出在使用了switchMap操作符——它的特性是每当新的源事件(这里就是PLAYER_SCRIPT_LOAD动作)触发时,会直接取消掉之前还在执行的内部Observable(也就是从Promise转换来的流)。

结合你的场景来看:第一次触发PLAYER_SCRIPT_LOAD后,脚本开始加载,Promise处于pending状态;此时第二次动作触发,switchMap会直接丢弃第一次的Promise,只处理第二次请求。而第二次调用injectScript时,脚本标签已经存在,会立即返回script-exists,最终你只会看到一次后续action被触发,这就导致了两次动作只得到一次响应的问题。

要让每个PLAYER_SCRIPT_LOAD动作都能触发对应的后续action,你需要替换掉switchMap,改用不会中断之前任务的操作符,下面是两种可行方案:

方案1:使用concatMap(顺序处理)

concatMap会严格按顺序处理每个源事件,只有前一个内部Observable完成后,才会处理下一个。这样两次动作会依次执行:第一次等待脚本加载完成,第二次立即返回脚本已存在的结果。

修改后的epic代码:

export default function loadPlayerScript (action$, store, { scriptLoaderHelper }) {
  return action$.ofType(PLAYER_SCRIPT_LOAD)
    .concatMap(action => Observable.fromPromise(scriptLoaderHelper.injectScript(action.data.id, action.data.script)))
    .map(data => {
      if (data === 'script-loaded') {
        return playerScriptSuccess();
      }
      return playerScriptExists();
    })
    .catch(error => Observable.of(playerScriptFailure(error)));
}

方案2:使用mergeMap(并行处理)

mergeMap允许同时处理多个源事件,支持多个内部Observable并行执行。在你的场景里,第二次请求会立即resolve,所以并行和顺序处理的结果差异不大,但如果后续有更复杂的异步场景,mergeMap会更灵活。

修改后的epic代码:

export default function loadPlayerScript (action$, store, { scriptLoaderHelper }) {
  return action$.ofType(PLAYER_SCRIPT_LOAD)
    .mergeMap(action => Observable.fromPromise(scriptLoaderHelper.injectScript(action.data.id, action.data.script)))
    .map(data => {
      if (data === 'script-loaded') {
        return playerScriptSuccess();
      }
      return playerScriptExists();
    })
    .catch(error => Observable.of(playerScriptFailure(error)));
}

额外优化建议

你还可以给injectScript函数添加脚本加载失败的错误处理,让Promise在加载出错时reject,这样epic的catch就能捕获错误并触发playerScriptFailure动作:

injectScript (id, src) {
  return new Promise((resolve, reject) => {
    if (!document.getElementById(id)) {
      const script = document.createElement('script');
      script.setAttribute('id', id);
      script.type = 'text/javascript';
      script.src = src;
      document.body.appendChild(script);
      script.onload = () => resolve('script-loaded');
      script.onerror = (error) => reject(error); // 添加加载失败的错误回调
    } else {
      resolve('script-exists');
    }
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:02:39