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

