背景概述
在我们日常开发中,我们常常需要在某个地方暂停某个动作一段时间。这个时候,我们的通常做法是使用setTimeout,配合promise实现。也就是如下代码。
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Delayed value');
}, ms);
});
}
async function example() {
console.log('Before delay');
await delay(2000); // 等待Promise的resolve操作完成
console.log('After delay');
}
但我们偶尔需要提前终止这个定时器。这时候我们会需要使用JavaScript 自带的clearTimeout方法,这个方法要求我们传入 timeoutId,也就是这个定时器的Id
const timeoutId = setTimeout(() => {
}, ms)
clearTimeout(timeoutId)
遭遇问题
如上所述,我们在某个地方暂停某个动作一段时间。这个时候,我们的通常做法是使用setTimeout,配合promise实现。又需要提前终止这个定时器。我们普遍的做法是让一个外部变量接受这个setTimeout的返回值。然后在需要终止他的地方,调用clearTimeout,终止这个定时器。
let timeoutId = '';
function delay(ms) {
const promise = new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
resolve("Delayed value");
}, ms);
});
return promise;
}
或者是内部定义方法
function delay(ms) {
let timeoutId; // 保存timeoutId的变量
const promise = new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
resolve("Delayed value");
}, ms);
});
// 添加一个stop方法,用于提前停止延迟操作
promise.stop = () => {
clearTimeout(timeoutId);
};
return promise;
}
但是这两种都有同样的一个问题
async function example() {
console.log("Before delay");
const promise = delay(2000); // 获取延迟操作的Promise对象
setTimeout(() => {
promise.stop(); // 提前停止延迟操作
console.log("Stopped delay");
}, 1000);
await promise; // 等待Promise的resolve操作完成
console.log("After delay");
}
example();
后续代码的 console.log(“After delay”)就至此不会再被执行了。
原因分析
await紧跟一个没有resolve/reject的promise对象,则后续的代码不会被执行。举个小例子
async function a(){
// 如果await后是promise对象
await new Promise(resolve => {
console.log(66666)
})
console.log(1) // 这一行并不会被执行到
}
a();
也就是说这里的 await 所等待的 promise的状态永远是处于 pending 状态的。
我们上述的代码中的resolve()在setTimeout内部,当定时器被提前终止的时候,定时器内部的回调函数不会被调用,也就是resolve从此不会被任何东西调用。导致 promise永远处于pending状态,而await 永远等待这个pending状态的代码执行。则await后续代码永远不会被执行。
解决方法
我们可以使用AbortController
和setTimeout
结合的方式来中断延迟操作,并在需要中断的时候调用abort
方法。这样,中断操作后的代码就会执行,从而实现提前停止延迟操作。
AbortController
通常用来终止web请求,和我们这个有异曲同工之妙。
解决代码如下
function delay() {
const controller = new AbortController();
const signal = controller.signal;
const promise: any = new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
resolve("Delayed value");
}, 60000);
signal.addEventListener("abort", () => {
clearTimeout(timeoutId);
resolve("Delayed value");
});
});
promise.stop = () => {
controller.abort();
};
return promise;
}
async function example() {
console.log('Before delay');
const promise = delay(2000);
setTimeout(() => {
promise.stop();
console.log('Stopped delay');
}, 1000);
await promise;
console.log('After delay');
}
example();
这样就能正常调用啦