await Promise内部执行setTimeout定时器,提前clearTimeout,导致卡死的情况分析及解决方案

背景概述

在我们日常开发中,我们常常需要在某个地方暂停某个动作一段时间。这个时候,我们的通常做法是使用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后续代码永远不会被执行。

解决方法

我们可以使用AbortControllersetTimeout结合的方式来中断延迟操作,并在需要中断的时候调用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();

这样就能正常调用啦
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值