如何取消回调内Pending Promise调用及解决变量更新不生效问题
会议就绪检测中断功能实现方案
问题场景
实现会议就绪检测流程时,需要支持用户随时退出测试的能力:当用户触发退出操作时,立即取消所有处于pending状态的await函数调用。当前核心问题:外部控制变量的值更新后,无法在当前异步代码块内获取到最新值,导致中断逻辑无法正常触发。
原有业务代码
fetch( "https://rqyhfmwv4b.execute-api.us-east-1.amazonaws.com/production/join-meeting", requestOptions ).then((response) => response.json()) .then(async (data) => { // 从服务端Chime API获取响应数据 const meetingResponse = data.result.meetingResponsee; // CreateMeeting接口返回值 const attendeeResponse = data.result.attendeeResponsee; // CreateAttendee/BatchCreateAttendee接口返回值 const configuration = new MeetingSessionConfiguration( meetingResponse, attendeeResponse ); // 初始化会议会话实例 const meetingSession = new DefaultMeetingSession( configuration, logger, deviceController ); const meetingReadinessChecker = new DefaultMeetingReadinessChecker( logger, meetingSession ); // 测试流程逻辑 if (pauseReadinessTest) { setPauseReadinessTest(false); return; } if (testParam == "All" && disableStartRedinessTest !== true) { await audioTest(deviceController); setMeetingTestProgress(15); await micTest(meetingSession, meetingReadinessChecker, CheckAudioInputFeedback); setMeetingTestProgress(25); await videoTest(meetingSession, meetingReadinessChecker, CheckVideoInputFeedback); await cameraTest(meetingSession, meetingReadinessChecker, CheckCameraResolutionFeedback); setMeetingTestProgress(50); await networkTest(meetingReadinessChecker, CheckNetworkTCPConnectivityFeedback, CheckNetworkUDPConnectivityFeedback); setMeetingTestProgress(75); await streamTest(meetingSession, meetingReadinessChecker, CheckAudioConnectivityFeedback, CheckVideoConnectivityFeedback); setMeetingTestProgress(85); await screenShareTest(meetingSession, meetingReadinessChecker, CheckContentShareConnectivityFeedback); setMeetingTestProgress(100); setRefreshIconClass(" "); SetDisableStartRedinessTest(false); setIsResult("result"); } })
根因分析
- 普通状态变量(比如React useState声明的控制变量)在异步闭包中捕获的是代码执行瞬间的快照值,后续状态更新不会同步到已经启动的异步闭包中,因此判断逻辑永远读不到最新的中断标记。
- 原生Promise/await没有外部取消机制,只要异步任务进入pending状态,没有专门的中断信号就无法主动终止执行。
修复实现
核心改造点
- 替换普通状态标记为可实时读取的引用:React场景用
useRef存储中断标记,ref的current属性可以在任意时刻读取到最新值,不存在闭包快照问题;原生JS场景直接用普通对象存标记即可。 - 引入AbortController实现全链路可取消:用AbortController的signal贯穿所有测试方法,触发退出时调用abort方法,所有监听该信号的异步任务会立即终止。
修复后核心代码
// 初始化中断控制器(放在组件/业务模块初始化位置) const abortController = new AbortController(); const abortSignal = abortController.signal; const isTestCanceled = { current: false }; // React场景替换为useRef({current: false}) // 用户触发退出测试时调用的方法 const handleCancelTest = () => { isTestCanceled.current = true; abortController.abort(); // 可在此处添加测试中断后的UI重置逻辑,比如重置进度、回到初始页 } fetch( "https://rqyhfmwv4b.execute-api.us-east-1.amazonaws.com/production/join-meeting", requestOptions ).then((response) => response.json()) .then(async (data) => { const meetingResponse = data.result.meetingResponsee; const attendeeResponse = data.result.attendeeResponsee; const configuration = new MeetingSessionConfiguration( meetingResponse, attendeeResponse ); const meetingSession = new DefaultMeetingSession( configuration, logger, deviceController ); const meetingReadinessChecker = new DefaultMeetingReadinessChecker( logger, meetingSession ); // 监听中断信号,触发时直接清理会话资源 abortSignal.addEventListener('abort', () => { meetingSession?.audioVideo?.stop(); deviceController?.destroy(); }, { once: true }); try { // 每个步骤执行前先判断中断标记 if (isTestCanceled.current) return; if (testParam == "All") { // 所有测试方法都接收abortSignal作为参数,内部监听abort事件主动reject终止 await audioTest(deviceController, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(15); await micTest(meetingSession, meetingReadinessChecker, CheckAudioInputFeedback, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(25); await videoTest(meetingSession, meetingReadinessChecker, CheckVideoInputFeedback, abortSignal); if (isTestCanceled.current) return; await cameraTest(meetingSession, meetingReadinessChecker, CheckCameraResolutionFeedback, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(50); await networkTest(meetingReadinessChecker, CheckNetworkTCPConnectivityFeedback, CheckNetworkUDPConnectivityFeedback, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(75); await streamTest(meetingSession, meetingReadinessChecker, CheckAudioConnectivityFeedback, CheckVideoConnectivityFeedback, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(85); await screenShareTest(meetingSession, meetingReadinessChecker, CheckContentShareConnectivityFeedback, abortSignal); if (isTestCanceled.current) return; setMeetingTestProgress(100); setRefreshIconClass(" "); SetDisableStartRedinessTest(false); setIsResult("result"); } } catch (err) { // 捕获abort触发的错误,不需要向上抛出 if (err.name !== 'AbortError') { console.error('测试流程异常', err); } } finally { // 无论中断还是正常完成,都做一次资源清理 if (isTestCanceled.current) { setMeetingTestProgress(0); setRefreshIconClass(" "); } } })
测试方法改造要求
所有自定义测试方法(audioTest、micTest等)内部需要接收abortSignal参数,在异步逻辑启动前先判断signal.aborted状态,同时添加abort事件监听,触发时直接reject返回AbortError,终止内部异步操作。以audioTest为例:
const audioTest = async (deviceController, signal) => { if (signal.aborted) return Promise.reject(new DOMException('测试已取消', 'AbortError')); return new Promise((resolve, reject) => { const onAbort = () => { // 停掉音频采集等内部逻辑 deviceController.stopAudioInput(); reject(new DOMException('测试已取消', 'AbortError')); }; signal.addEventListener('abort', onAbort, { once: true }); // 原有音频测试逻辑 runAudioTestLogic().then(resolve).catch(reject).finally(() => { signal.removeEventListener('abort', onAbort); }); }) }
内容的提问来源于stack exchange,提问作者user6634092
相关产品推荐
相关产品推荐

