0
点赞
收藏
分享

微信扫一扫

#yyds干货盘点#可以中断的异步操作

由于 JavaScript 的单线程特性,能在 JavaScript 中进行的异步场景其实不多,大概有如下一些:

  1. ​setTimeout()​​ /​​setInterval()​
  2. 事件
  3. Ajax
  4. 部分对 Native 方法的调用
  5. ……


中断 Ajax 操作

Ajax 处理基本上也可以归为“对 Native 方法调用”一类,因为基本上都是由浏览器提供的 ​​XMLHttpRequest​​ 或者 ​​fetch()​​ 来实现的。所以 Axios、fetch() 和 jQuery.ajax() 等,自己都提供了 abort 的接口。中断 ​​fetch()​​ 已经在 「​​可能超时的异步操作​​」中已经有示例了,这里再给个 jQuery 和 Axios 的示例。

jQuery 的 jqXHR 提供了 .abort()

// url 是在 beeceptor.com 上做的一个要 3 秒后响应的 GET 接口
const fetching = $.ajax(url, { type: "get" })
.done(() => console.log("看不到这句话"))
.fail(() => console.log("但是能看到这句"));

setTimeout(() => fetching.abort(), 1000); // 1 秒后中断请求

也可以用 ​​await​​ 的方式来写:

(async () => {
try {
const fetching = $.ajax(url, { type: "get" });
setTimeout(() => fetching.abort(), 1000);
await fetching;
console.log("看不到这句话");
} catch (err) {
console.log("但是能看到这句");
}
})();

中断 Axios 请求

Axios 提供了 ​​CancelToken​​ 来实现中断,这个模式和在前文中中断 ​​fetch()​​ 的 ​​AbortController​​ 和 ​​AbortSignal​​ 是同样的道理。

// Node 中需要 import;浏览器中直接引用的 axios.js 会有全局 axios 对象
import axios from "Axios";

(async () => {
const { CancelToken } = axios;
const source = CancelToken.source(); // 创建一个中断源

try {
setTimeout(() => source.cancel("1 秒中断"), 1000);
const data = await axios.get(
url, // beeceptor.com 上做的一个要 3 秒后响应的 GET 接口
{
cancelToken: source.token // 把 token 传进去
}
);
console.log("因为超时中断,看不到这句话");
} catch (err) {
if (axios.isCancel(err)) {
console.log("超时中断了 Axios 请求", err);
// 超时中断了 Axios 请求 Cancel { message: '1 秒中断' }
} else {
console.log("发生其他错误");
}
}
})();

中断定时器和事件

对 ​​setTiemout()​​​ / ​​setInteraval()​​​ 的中断,可以说是比较简单,使用 ​​clearTimeout()​​​ / ​​clearInterval()​​ 就可以办到。

而中断事件 —— 直接注销事件处理函数就好了。不过需要注意的是,部分事件框架在注销事件的时候需要提供注册的那个事件处理函数才有注销,比如 ​​removeEventListener()​​ 就是需要提供原处理函数;而 jQuery 通过 ​​.off()​​注销事件处理函数时只需要提供名称和 namespace(如果有的话)即可。

不过当这些过程封装在 Promise 中的时候,记得要在“注销”处理的时候 reject(当然,如果约定好了 resolve 一个特殊值也可以)。以 ​​setTimeout​​ 为例:

async function delayToDo() {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve("延迟后拿到这段文本");
}, 5000);
setTimeout(() => {
clearTimeout(timer);
reject("超时了");
}, 1000);
});
}

可以说,这段代码是相当没用 —— 谁会没事在设定一个延时任务之后立即设置一个更短的超时操作?


带出一个 abort() 函数

用转运箱对象把 abort() 运出来

注意 ​​delayToDo()​​ 不是一个 ​​async​​ 函数。如果使用 ​​async​​ 修饰,我们是拿不到 ​​return​​ 的 ​​promise​​ 的。在确实需要用 ​​async​​ 修饰的情况下,只好变通一下,通过一个“转运箱”对象把 ​​abort()​​ 带出来。

function delayToDo(ms, transferBox) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve("延迟后拿到这段文本");
}, ms);

// 如果有转运箱,就把 abort 函数给运出去
if (transferBox) transferBox.abort = (message) => {
clearTimeout(timer);
reject({ abort: true, message });
};
});
}

// 定义一个转运箱对象,注意作用域(所以定义在 IIFE 外面)
const box = {};

(async () => {
try {
const s = await delayToDo(5000, box);
console.log("不会输出这句", s);
} catch (err) {
console.log("出错", err);
}
})();


如果我们需要设置一个延时任务,并在后面某种情况下中断它,正确的做法是把 ​​timer​​ 带到延时任务函数外面去,以便其他地方使用。而更好的办法是带出一个 ​​abort()​​ 函数,使语义更准确。


function delayToDo(ms) {
let timer;
const promise = new Promise(resolve {
timer = setTimeout(() => {
resolve("延迟后拿到这段文本");
}, ms);
});
promise.abort = () => clearTimeout(timer);
return promise;
}

const promise = delayToDo(5000);

// 在其他业务逻辑中通过 promise.abort() 来中断延时任务
setTimeout(() => promise.abort(), 1000);

举报

相关推荐

0 条评论