异步中止神器之AbortController


‌AbortController‌ 是 JavaScript 中的一个全局类,主要用于中止正在进行或待完成的异步操作,如 fetch 请求、事件监听、可写流、数据库事务等。通过 AbortController,我们可以在需要时自由地终止这些操作,避免不必要的开销或冗长的等待。

基本用法:

  1. ‌ 创建 AbortController 实例 ‌:通过 new AbortController()创建一个控制器实例。
  2. ‌ 获取 signal 对象 ‌:通过 controller.signal 获取一个 AbortSignal 对象,该对象可以传递给需要中止的 API。
  3. ‌ 中止操作 ‌:调用 controller.abort()方法会触发与该控制器关联的所有操作的中止。

使用实例:

  1. fetch 使用 AbortController
const controller = null;
const fetchApply = async () => {
    controller = new AbortController();
    const signal = controller?.signal;

    try {
        let params = {};
        const response = await fetch("http://127.0.0.1", {
            method: "POST",
            headers: {
                "Content-Type": "application/json", // 设置请求头,表明发送的内容是 JSON
            },
            body: JSON.stringify(params),
            signal: signal,
        });
        if (!response.ok) {
            throw new Error("网络响应不正常");
        }
    } catch (err) {
        if (err.name === "AbortError") {
            console.log("AbortError-请求被取消");
        } else {
            console.log(err.message, "err.message");
        }
    }
};
  1. axios 使用 AbortController
const axiosApply = async () => {
    controller = new AbortController();
    const signal = controller?.signal;

    try {
        let params = {};
        const response = await axios({
            url: "http://127.0.0.1",
            method: "POST",
            headers: {
                "Content-Type": "application/json", // 设置请求头,表明发送的内容是 JSON
            },
            data: params,
            signal,
        });
        if (!response.ok) {
            throw new Error("网络响应不正常");
        }
    } catch (err) {
        if (err.name === "CanceledError") {
            console.log("AbortError-请求被取消");
        } else {
            console.log(err.message, "err.message");
        }
    }
};
  1. 取消请求
const cancelRequest = () => {
    if (controller) {
        controller?.abort(); // 取消请求
    }
};
  1. 基于 AbortController 构建轮询函数
/**
 * 轮询函数(支持取消、指数退避、错误处理)
 * @param {*} params 请求参数
 * @param {*} options 处理参数
 * @returns
 */
const polling = async (params, options = {}) => {
    const {
        maxRetries = 10, // 最大重试次数
        baseDelay = 1000, // 基础延迟(毫秒)
        maxDelay = 30000, // 最大延迟(毫秒)
        signal = null, // 取消信号(AbortController)
    } = options;

    let retryAttempts = 0; // 错误重试计数器
    let pollingAttempts = 0; // 轮询尝试计数器
    let lastError = null;

    const calculateDelay = attempt => {
        return Math.min(baseDelay * 2 ** attempt, maxDelay);
    };
    // 1. 检查是否已取消
    const waitWithAbort = delay => {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(resolve, delay);

            // 支持延迟期间取消
            if (signal) {
                signal.addEventListener(
                    "abort",
                    () => {
                        clearTimeout(timer);
                        reject(new Error("轮询已取消"));
                    },
                    { once: true }
                );
            }
        });
    };

    while (true) {
        try {
            if (signal?.aborted) {
                throw new Error("轮询已取消");
            }

            // 2. 发起查询请求
            const { code, data, msg } = await queryAsrTask(params, signal);
            pollingAttempts++;

            // 3. 处理响应逻辑
            if (code === 200) {
                if (data.is_finish) {
                    return data.result; // 任务完成,返回结果
                } else {
                    // 任务未完成时使用独立延迟计算
                    const delay = calculateDelay(pollingAttempts);
                    await waitWithAbort(delay);
                }
            } else {
                // 非200响应,抛出错误终止轮询
                throw new Error(msg || "后端返回未知错误");
            }
        } catch (error) {
            // 4. 统一错误处理
            if (
                error.name === "AbortError" ||
                error.code === 400 || // 示例:客户端错误不重试
                retryAttempts >= maxRetries
            ) {
                if (!lastError) {
                    console.log("错误", error.message);
                }
                throw error;
            }

            // 记录错误并应用退避
            lastError = error;
            retryAttempts++;

            // 指数退避 + 抖动 (jitter)
            const delay = calculateDelay(retryAttempts);
            const jitter = delay * 0.2 * Math.random();
            await waitWithAbort(delay + jitter);
        }
    }
};

// 使用示例
const controller = new AbortController();

// 启动轮询
polling(params, { maxRetries: 8, baseDelay: 1500, signal: controller.signal })
    .then(data => console.log("任务完成:", data))
    .catch(error => {
        if (error.message.includes("取消")) {
            console.log("用户主动取消了轮询");
        } else {
            console.error("任务失败:", error);
        }
    });

// 取消轮询
// 用户点击取消按钮时调用
document.getElementById("cancel-button").addEventListener("click", () => {
    controller.abort();
});

在 JavaScript 中,任何被声明为 async 的函数都会自动返回一个 Promise。这是 async 函数的一个特性,无论函数内部是否使用了 await,只要函数被声明为 async,它的返回值就会被自动包装在一个 Promise 中。


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录