JS `setTimeout` / `setInterval` 结合 `Promise` / `async/await` 实现延迟/重复任务

各位靓仔靓女,晚上好!我是你们今晚的讲师,老码。今天咱们聊点好玩的,关于JavaScript里setTimeoutsetInterval这俩兄弟,怎么跟Promiseasync/await这俩时髦精搞基(咳咳,合作)。这可不是简单的1+1=2,搞好了,能让你的代码更优雅,更易读,更逼格。

Part 1: setTimeoutPromise的爱恨情仇

首先,咱们得了解一下setTimeout这家伙。它本质上是个定时器,让你在指定的时间后执行一段代码。但是,它返回的不是Promise,这让很多习惯了Promise编程的同学很不爽。

举个栗子:

function sayHelloAfterDelay(delay) {
  setTimeout(() => {
    console.log("Hello after " + delay + "ms!");
  }, delay);
}

sayHelloAfterDelay(2000); // 2秒后输出 Hello after 2000ms!

这段代码没毛病,但是,如果我想在sayHelloAfterDelay执行完毕后,再做点别的事情呢?用回调地狱来解决吗?No way! 这时候,Promise就该闪亮登场了。

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function sayHelloAfterDelay(delayTime) {
  console.log("开始等待...");
  await delay(delayTime);
  console.log("Hello after " + delayTime + "ms!");
}

sayHelloAfterDelay(2000);

// 甚至可以链式调用
sayHelloAfterDelay(2000)
  .then(() => console.log("第一个sayHello结束"))
  .then(() => sayHelloAfterDelay(1000))
  .then(() => console.log("第二个sayHello结束"));

这段代码里,我们定义了一个delay函数,它返回一个Promise。这个Promise会在ms毫秒后resolve。 然后,我们在sayHelloAfterDelay函数里,使用await来等待这个Promise resolve。 这样,我们就可以用Promise链式调用的方式,来控制setTimeout的执行顺序了。

划重点: delay函数是关键,它把setTimeout变成了Promise,让我们可以用async/await来优雅地控制异步流程。

Part 2: setIntervalPromise的相爱相杀

setInterval这家伙,比setTimeout更猛,它会按照指定的时间间隔,不停地执行一段代码。 这就引出了一个问题:如果每次执行的代码需要花费的时间超过了时间间隔,那就会出现问题。

举个栗子:

let count = 0;
setInterval(() => {
  // 模拟耗时操作
  for (let i = 0; i < 100000000; i++) {
    // do nothing
  }
  console.log("Interval: " + count++);
}, 100);

这段代码里,我们用一个循环来模拟耗时操作。 如果循环执行的时间超过了100ms,那么下一次setInterval的回调函数就会在上次还没执行完的时候就开始执行了。 这会导致代码执行变得混乱,甚至崩溃。

这时候,Promise又该出马了。我们可以用async/await来确保每次回调函数执行完毕后,再执行下一次回调函数。

async function intervalAsync(callback, interval) {
  while (true) {
    await callback(); // 等待回调函数执行完毕
    await delay(interval); // 等待指定的时间间隔
  }
}

let countInterval = 0;
intervalAsync(async () => {
  // 模拟耗时操作
  for (let i = 0; i < 100000000; i++) {
    // do nothing
  }
  console.log("Async Interval: " + countInterval++);
}, 100);

这段代码里,我们定义了一个intervalAsync函数,它接受一个回调函数和一个时间间隔作为参数。 在intervalAsync函数里,我们用一个while(true)循环来不停地执行回调函数。 每次执行回调函数之前,我们都用await来等待它执行完毕。 这样,就可以确保每次回调函数执行完毕后,再执行下一次回调函数,避免了代码执行混乱的问题。

划重点: intervalAsync函数是关键,它用async/await来确保每次回调函数执行完毕后,再执行下一次回调函数,避免了并发问题。

Part 3: 实战演练:模拟网络请求重试

咱们来个更实际的例子:模拟网络请求重试。 在网络不稳定的情况下,我们经常需要重试网络请求。 我们可以用setTimeoutPromise来实现这个功能。

async function fetchData(url, maxRetries = 3, retryDelay = 1000) {
  let retryCount = 0;
  while (retryCount < maxRetries) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return await response.json(); // 返回解析后的 JSON 数据
    } catch (error) {
      console.error(`Request failed (attempt ${retryCount + 1}):`, error);
      retryCount++;
      if (retryCount >= maxRetries) {
        throw error; // 如果达到最大重试次数,则抛出错误
      }
      await delay(retryDelay); // 等待一段时间后重试
    }
  }
}

// 使用示例
fetchData("https://rickandmortyapi.com/api/character") // 模拟一个API
  .then(data => console.log("Data fetched:", data))
  .catch(error => console.error("Failed to fetch data after retries:", error));

这段代码里,我们定义了一个fetchData函数,它接受一个URL、最大重试次数和一个重试延迟作为参数。 在fetchData函数里,我们用一个while循环来不停地尝试发送网络请求。 如果请求失败,我们会等待一段时间后重试。 如果达到最大重试次数,我们会抛出错误。

划重点: 这个例子展示了如何用setTimeoutPromise来实现一个实用的功能:网络请求重试。

Part 4: 避坑指南:一些需要注意的地方

  • 内存泄漏: setInterval如果不clearInterval,会一直执行下去,可能会导致内存泄漏。 所以,记得在不需要的时候,clearInterval。
  • 时间精度: setTimeoutsetInterval的时间精度不是绝对准确的。 浏览器的计时器可能会受到其他任务的影响。
  • this指向: setTimeoutsetInterval的回调函数里的this指向可能不是你期望的。 可以使用箭头函数来解决这个问题。
  • 错误处理:async/await代码里,要记得使用try/catch来处理错误。

Part 5: 总结与展望

今天,咱们一起学习了setTimeoutsetInterval怎么跟Promiseasync/await一起玩耍。 掌握了这些技巧,可以让你写出更优雅、更易读、更逼格的代码。

最后,送给大家一句箴言:

代码虐我千百遍,我待代码如初恋。

咱们下期再见!

附录:常用技巧总结

技巧 描述 代码示例
setTimeout转Promise setTimeout 转换为 Promise,使其可以与 async/await 一起使用,方便控制异步流程。 javascript function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function example() { console.log("开始等待..."); await delay(2000); console.log("等待结束!"); } example();
异步setInterval 使用 async/await 实现的 setInterval,确保每次回调函数执行完毕后,再执行下一次回调函数,避免并发问题。 javascript async function intervalAsync(callback, interval) { while (true) { await callback(); await delay(interval); } } let count = 0; intervalAsync(async () => { console.log("Interval: " + count++); }, 1000);
网络请求重试 使用 setTimeoutPromise 实现网络请求重试机制,在网络不稳定的情况下提高应用的健壮性。 javascript async function fetchData(url, maxRetries = 3, retryDelay = 1000) { // ... (代码见上文) }
清除定时器 使用 clearIntervalclearTimeout 清除不再需要的定时器,防止内存泄漏。 javascript let intervalId = setInterval(() => { console.log("Running..."); }, 1000); setTimeout(() => { clearInterval(intervalId); console.log("Stopped!"); }, 5000);
避免this指向问题 使用箭头函数可以避免 setTimeoutsetInterval 回调函数中的 this 指向问题。 javascript class MyClass { constructor() { this.name = "MyClass"; } greetAfterDelay(delay) { setTimeout(() => { console.log("Hello from " + this.name); // this 指向 MyClass 实例 }, delay); } } const myInstance = new MyClass(); myInstance.greetAfterDelay(1000);
使用try...catch处理错误 async/await 代码中使用 try...catch 块来捕获并处理异步操作中可能发生的错误。 javascript async function fetchData(url) { try { const response = await fetch(url); const data = await response.json(); return data; } catch (error) { console.error("Error fetching data:", error); throw error; // 重新抛出错误 } }

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注