广告

JS中函数延迟执行的几种实现方式:定时器、Promise/async-await、以及防抖与节流

1. 使用定时器实现函数延迟执行

在前端开发中,函数延迟执行是一类常见需求,尤其是在用户交互后需要再处理的场景。通过定时器机制,可以把某段代码在未来某个时刻执行,从而实现 单次延迟执行周期性执行 的控制。

定时器 主要依赖浏览器的计时队列,常见的有 setTimeoutsetInterval,它们分别对应一次性延迟和周期性执行。使用时需要注意对取消、清理、以及页面隐藏时的行为进行处理,以避免潜在的内存泄漏。

定时器的基本用法

下面展示的是一个简单的 setTimeout 的用法示例,用来实现一个单次的延迟执行。通过返回值可以在需要时进行取消。重要点在于:明确清理定时器,避免回调在不需要时仍然执行。

// 单次延迟执行
function delay(ms, fn) {return setTimeout(fn, ms);
}// 取消定时器示例
const t = delay(1000, () => console.log('延迟1秒执行'));
clearTimeout(t);

如果需要持续性的节奏性触发,可以使用 setInterval,但应结合条件进行 及时清理,以防止长期执行导致资源占用过高。

// 周期性执行示例
const intervalId = setInterval(() => {console.log('tick');
}, 1000);// 5 秒后停止
setTimeout(() => clearInterval(intervalId), 5000);

在实际场景中,定时器还涉及到边缘情况的处理,例如页面可见性变化、浏览器休眠后回调是否重新计划等,正确处理清理与可预测性是提升稳定性的关键。

2. 基于 Promise/async-await 的延迟执行实现

在现代 JavaScript 中,Promise 提供了一个更清晰的异步控制流,将延迟逻辑从回调地狱中解放出来。通过把延迟封装为一个可返回的 Promise,可以与 async/await 配合,获得更易读的代码。

sleep 函数是最常见的 Promise 延迟实现,它内部通过 setTimeout 来等待指定时间,并在到时后完成 Promise 的 resolve

Promise 的延迟实现

// Promise 风格的延迟实现
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

使用 Promise 的一个常见方式是通过 async/await 进行顺序执行,使延迟的语义更接近同步代码,便于理解和维护。需要注意的是,await 只是让后续代码等待,不会阻塞整个线程。

async function run() {console.log('start');await sleep(1000); // 延迟1秒console.log('after 1s');
}
run();

若有多个独立的延迟任务,可以借助 Promise.all 实现并发等待,提升并发效率,同时也要关注异常处理与超时等边界情况。

async function runAll() {await Promise.all([sleep(1000), sleep(2000)]);console.log('两次延迟完成');
}

通过这些模式,可以实现灵活的异步控制:顺序执行并发执行、以及对超时或错误的统一处理,使代码结构更清晰、可维护性更高。

3. 防抖与节流在延迟执行中的应用

防抖( debounce )和节流( throttle )是两种常用的控制高频事件触发频次的技术,广泛应用于 输入搜索、滚动监听、窗口调整等场景。它们的核心都在于通过一定的策略,将多次触发尽量合并为较少次数的实际执行,从而实现更稳定的性能表现。

在实现上,防抖与节流都需要保存状态(如定时器、上次执行时间),并在触发时对执行时机进行控制。理解其差异有助于在不同场景下选择合适的实现策略。

防抖(debounce)实现

防抖的核心思想是:在一段持续输入的时间内,只在最后一次触发结束后执行一次回调。对搜索框、自动完成等场景尤为合适。实现时要考虑参数传递和上下文绑定,以及可选的 leadingtrailing 行为。

// 简单的防抖实现
function debounce(fn, delay) {let timer;return function(...args) {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);};
}

下面是一个带入参的使用示例,典型用于文本框输入事件的防抖处理:当用户停止输入一段时间后再触发查询逻辑,避免无谓的频繁请求。

const search = debounce((query) => {console.log('搜索:', query);
}, 300);// 假设这是一个输入事件处理器
// document.getElementById('input').addEventListener('input', (e) => search(e.target.value));

在实际应用中,可以通过添加 leading(首次触发)或 trailing(结束触发)的选项来微调行为,以满足不同 UX 需求。

节流(throttle)实现

节流的目标是在固定时间间隔内最多执行一次回调,常用于监听滚动、调整大小等高频事件,以减少回调次数。实现方式通常可选即时执行(leading)或末尾执行(trailing),也可以组合两者。

// 简单的节流实现
function throttle(fn, limit) {let last = 0;let timer;return function(...args) {const now = Date.now();const remaining = limit - (now - last);if (remaining <= 0) {last = now;fn.apply(this, args);} else {clearTimeout(timer);timer = setTimeout(() => {last = Date.now();fn.apply(this, args);}, remaining);}};
}

节流在滚动监听、窗口调整等场景下尤为有用,可以显著降低对页面布局和网络请求的压力,确保用户交互的平滑性。通过合理设置 limit,可以在响应及时性和性能之间取得平衡。

JS中函数延迟执行的几种实现方式:定时器、Promise/async-await、以及防抖与节流

广告