各位靓仔靓女,晚上好!我是你们今晚的讲师,老码。今天咱们聊点好玩的,关于JavaScript里setTimeout
、setInterval
这俩兄弟,怎么跟Promise
、async/await
这俩时髦精搞基(咳咳,合作)。这可不是简单的1+1=2,搞好了,能让你的代码更优雅,更易读,更逼格。
Part 1: setTimeout
与Promise
的爱恨情仇
首先,咱们得了解一下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: setInterval
与Promise
的相爱相杀
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: 实战演练:模拟网络请求重试
咱们来个更实际的例子:模拟网络请求重试。 在网络不稳定的情况下,我们经常需要重试网络请求。 我们可以用setTimeout
和Promise
来实现这个功能。
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
循环来不停地尝试发送网络请求。 如果请求失败,我们会等待一段时间后重试。 如果达到最大重试次数,我们会抛出错误。
划重点: 这个例子展示了如何用setTimeout
和Promise
来实现一个实用的功能:网络请求重试。
Part 4: 避坑指南:一些需要注意的地方
- 内存泄漏:
setInterval
如果不clearInterval,会一直执行下去,可能会导致内存泄漏。 所以,记得在不需要的时候,clearInterval。 - 时间精度:
setTimeout
和setInterval
的时间精度不是绝对准确的。 浏览器的计时器可能会受到其他任务的影响。 - this指向:
setTimeout
和setInterval
的回调函数里的this
指向可能不是你期望的。 可以使用箭头函数来解决这个问题。 - 错误处理: 在
async/await
代码里,要记得使用try/catch
来处理错误。
Part 5: 总结与展望
今天,咱们一起学习了setTimeout
和setInterval
怎么跟Promise
和async/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); |
网络请求重试 | 使用 setTimeout 和 Promise 实现网络请求重试机制,在网络不稳定的情况下提高应用的健壮性。 |
javascript async function fetchData(url, maxRetries = 3, retryDelay = 1000) { // ... (代码见上文) } |
清除定时器 | 使用 clearInterval 或 clearTimeout 清除不再需要的定时器,防止内存泄漏。 |
javascript let intervalId = setInterval(() => { console.log("Running..."); }, 1000); setTimeout(() => { clearInterval(intervalId); console.log("Stopped!"); }, 5000); |
避免this 指向问题 |
使用箭头函数可以避免 setTimeout 和 setInterval 回调函数中的 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; // 重新抛出错误 } } |