- 设置一个flag标志位,点击按钮的时候改变标志位为true,在三个await中添加判断,如果标志位为true,则根据layerName清楚路径的layer,然后return函数,终止后面的await函数逻辑。
终止异步函数执行并撤销 UI 更新的方法
在 JavaScript 中,异步函数(如 createHistoryWays
)的终止不是内置的(不像线程的 kill
),因为 Promise 一旦启动就无法直接 "中断" 执行链。但可以通过 AbortController (现代浏览器/Node.js 支持,推荐)、状态标志 或 事件驱动 机制来实现近似取消。终止的核心是:
- 中断内部异步操作 :如
gisManager.getGis().routePlan(routes)
(假设它是基于fetch
或类似可取消的 Promise;如果不是,需要修改其实现以支持取消)。 - 停止后续函数:在调用后续函数前检查取消信号。
- 撤销 UI 更新:假设这些函数会更新 UI(e.g., 地图库如 Leaflet、Google Maps 或 ArcGIS 绘制路线/图标),需要在取消时调用清除方法(如移除 polyline 图层、图标标记)。
以下分析在执行 createHistoryWays(multiHistoryWayPoints)
的 20s 内(总 ~30s)触发终止的各种方法。我假设:
gisManager.getGis().routePlan
是异步的(返回 Promise),并可通过 AbortSignal 取消(如果不是,需要先修改其内部实现,例如包装在fetch
中)。- UI 更新发生在
routePlan
的.then
回调中(e.g., 绘制地图元素),因此需要跟踪这些元素以便清除。 - 终止触发:通过外部事件(如用户点击 "取消" 按钮)、定时器(
setTimeout
20s 后触发)或状态变化。 - 整体上下文:在
initFmeWayPoints
的.then
回调中修改调用方式,使用 async/await 以便控制。
1. 推荐方法:使用 AbortController(支持取消 Promise 和信号传播)
AbortController 是 Web API 标准(Node.js 15+ 支持),允许创建 AbortSignal,并在函数中检查/传播它。适用于取消 fetch
、XMLHttpRequest
或自定义 Promise。
步骤:
- 创建 AbortController。
- 将其
signal
传递给每个函数(修改createHistoryWays
和createWaytoTarget
以接受signal
参数,并在routePlan
中使用)。 - 在 20s 内触发
abortController.abort()
(e.g., 通过按钮或定时器)。 - 在 abort 时,调用 UI 清除函数(e.g.,
clearHistoryRoutes()
、clearPeopleIcons()
、clearTargetRoutes()
)。 - 后续函数:在 await 前检查
signal.aborted
,如果已取消,则跳过。
代码修改示例:
javascript
// 全局或模块级 UI 清除函数(假设地图库有方法移除元素)
let historyRouteLayers = []; // 跟踪历史路线图层
let peopleIconMarkers = []; // 跟踪人员图标
let targetRouteLayers = []; // 跟踪目标路线图层
function clearHistoryRoutes() {
historyRouteLayers.forEach(layer => gisManager.getGis().removeLayer(layer)); // 示例:移除地图图层
historyRouteLayers = [];
}
function clearPeopleIcons() {
peopleIconMarkers.forEach(marker => gisManager.getGis().removeMarker(marker));
peopleIconMarkers = [];
}
function clearTargetRoutes() {
targetRouteLayers.forEach(layer => gisManager.getGis().removeLayer(layer));
targetRouteLayers = [];
}
// 修改 createHistoryWays 以支持取消和 UI 跟踪
async function createHistoryWays(routes, signal = null) {
if (!Array.isArray(routes) || routes.length === 0) return;
try {
// 假设 routePlan 支持 signal(如果基于 fetch: fetch(url, { signal }))
const result = await gisManager.getGis().routePlan(routes, signal); // 传递 signal
// UI 更新:在 .then 中(但由于 await,这里是同步)
historyRouteLayers.push(result.routeLayer); // 假设 routePlan 返回图层对象
} catch (err) {
if (signal && err.name === 'AbortError') {
console.log('createHistoryWays aborted');
clearHistoryRoutes(); // 立即撤销 UI
return; // 退出
}
throw err; // 其他错误向上抛
}
}
// 类似修改 createWaytoTarget
async function createWaytoTarget(routes, signal = null) {
// ... 类似逻辑,跟踪 targetRouteLayers,并在 catch 中 clearTargetRoutes()
}
// createFmePeoplelcon 假设同步,但如果异步,也支持 signal
function createFmePeoplelcon(locations, signal = null) {
if (signal?.aborted) return; // 检查已取消
// UI 更新:添加图标
locations.forEach(loc => {
const marker = gisManager.getGis().addMarker(loc); // 示例
peopleIconMarkers.push(marker);
});
}
// 在 initFmeWayPoints 的 .then 中使用
.then(async (res) => {
// ... 数据处理逻辑 ...
const abortController = new AbortController(); // 创建控制器
const { signal } = abortController;
// 外部触发终止:e.g., 20s 定时器,或按钮事件
const timeoutId = setTimeout(() => {
abortController.abort(); // 触发取消
clearAllUI(); // 全局清除(可选)
}, 20000); // 20s 后取消
// 或者通过事件:document.getElementById('cancelBtn').addEventListener('click', () => abortController.abort());
try {
await createHistoryWays(multiHistoryWayPoints, signal); // 启动,传递 signal
// 在 20s 内如果 aborted,这里会抛 AbortError 并 catch 中清除
if (signal.aborted) {
console.log('Operation cancelled after history ways');
return; // 停止后续
}
createFmePeoplelcon(fmePeopleLocation, signal); // 检查 aborted
if (signal.aborted) return;
await createWaytoTarget(multiCurToTargetWays, signal);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Overall operation aborted');
// 撤销所有 UI
clearHistoryRoutes();
clearPeopleIcons();
clearTargetRoutes();
} else {
// 处理其他错误
throw err;
}
} finally {
clearTimeout(timeoutId); // 清理定时器
}
});
// 全局清除函数
function clearAllUI() {
clearHistoryRoutes();
clearPeopleIcons();
clearTargetRoutes();
}
执行时机和影响:
- 终止时机 :在 20s 内
abort()
触发时,routePlan
的 Promise 会 reject 以AbortError
,createHistoryWays
catch 它并清除 UI。随后检查signal.aborted
阻止后续函数启动。 - UI 撤销 :在 catch 或专用清除函数中立即调用,确保部分绘制的路线/图标被移除(e.g., 如果
routePlan
在 15s 开始绘制,20s 时移除)。 - 优点:优雅、标准;信号可传播到嵌套 Promise;不阻塞其他代码。
- 缺点 :需要修改
routePlan
支持 signal(e.g., 如果是第三方 API,包装它:new Promise((resolve, reject) => { /* 调用 API */ signal.addEventListener('abort', () => reject(new AbortError())) })
)。如果routePlan
不可取消,abort 只会停止等待,但后台可能继续(需检查其实现)。 - 浏览器兼容:IE 不支持,需 polyfill。
- 总时间:如果取消,~20s 内完成清理;否则正常 ~30s+。
2. 简单方法:使用状态标志(布尔变量或 Promise)
如果 AbortController 不可用或 routePlan
难修改,使用共享的 isCancelled
标志。函数内部轮询检查(e.g., 在 setInterval 或 Promise 链中)。
代码示例:
javascript
let isCancelled = false; // 全局/作用域标志
// 修改 createHistoryWays(简化版,无需 signal)
async function createHistoryWays(routes) {
if (isCancelled) return;
if (!Array.isArray(routes) || routes.length === 0) return;
// 模拟内部检查(如果 routePlan 不可取消,这里只能停止后续链)
try {
const result = await gisManager.getGis().routePlan(routes);
if (isCancelled) {
clearHistoryRoutes(); // 撤销
return;
}
// UI 更新...
} catch (err) {
if (isCancelled) clearHistoryRoutes();
throw err;
}
}
// 在 .then 中
.then(async (res) => {
// ... 数据处理 ...
const timeoutId = setTimeout(() => {
isCancelled = true;
clearAllUI(); // 立即撤销
}, 20000);
try {
await createHistoryWays(multiHistoryWayPoints);
if (isCancelled) return; // 停止后续
createFmePeoplelcon(fmePeopleLocation);
if (isCancelled) return;
await createWaytoTarget(multiCurToTargetWays);
} catch (err) {
// 处理错误
} finally {
clearTimeout(timeoutId);
isCancelled = false; // 重置
}
});
执行时机和影响:
- 终止时机 :20s 后设置
isCancelled = true
,后续 await resolve 后检查并停止。UI 在检查点清除。 - 优点 :简单,无需修改
routePlan
;适用于纯 JS 环境。 - 缺点 :不精确(如果
routePlan
在后台继续运行,无法真正中断;需在routePlan
内部添加检查,但复杂)。轮询开销小,但不优雅。 - 适用 :快速原型;如果
routePlan
是黑盒。
3. 事件驱动方法:使用 CustomEvent 或 PubSub 库
对于复杂应用,使用事件系统(e.g., 原生 CustomEvent 或库如 EventEmitter)触发取消。
代码示例(简要):
javascript
// 发布/订阅
document.addEventListener('cancelOperation', () => {
isCancelled = true; // 或触发清除
clearAllUI();
});
// 触发:setTimeout(() => document.dispatchEvent(new CustomEvent('cancelOperation')), 20000);
// 在函数中:addEventListener('cancelOperation', () => { clearHistoryRoutes(); });
执行时机和影响:
- 终止时机:事件触发后,监听器立即清除 UI 并设置标志停止后续。
- 优点:解耦(UI 按钮可直接触发);易扩展到多组件。
- 缺点:需管理事件监听器(避免内存泄漏);不直接取消 Promise。
- 适用:UI 重应用(e.g., React/Vue 中用 hooks 监听事件)。
总体建议和注意事项
- 最佳实践 :优先 AbortController,它最可靠且可撤销 UI(通过 catch 中的清除)。测试时,模拟
routePlan
延迟(await new Promise(r => setTimeout(r, 30000))
)和 abort。 - UI 撤销挑战:确保跟踪所有更新的元素(e.g., 用数组存储 ID/引用)。如果地图库有事务支持(e.g., undo stack),用它简化清除。
- 错误处理 :在取消时日志记录(e.g.,
console.log('Cancelled at 20s')
),并通知用户(e.g., toast 消息)。 - 性能影响:取消后,GIS 服务可能仍有残留请求(取决于服务器);监控网络(DevTools)。
- 限制 :如果
routePlan
是不可取消的第三方(e.g., 阻塞式),只能 "软取消"(停止等待 + 清除 UI),后台可能继续消耗资源。考虑替换为支持取消的实现。 - 测试:在 10s/20s 点手动触发取消,验证 UI 恢复原状和后续不执行。
如果提供 gisManager.getGis().routePlan
的具体实现或 UI 库细节,我可以给出更精确的代码。