jsly 3.1.6 → 3.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -227,3 +227,72 @@ color: '#0f0'
227
227
  | `config.tag` | `string` | `'span'` | 包裹关键词的标签名 |
228
228
  | `config.bgColor` | `string` | `'#ff0'` | 背景颜色 |
229
229
  | `config.color` | `string` | `'#001'` | 文本颜色 |
230
+
231
+ ### 基于 requestAnimationFrame 的定时器(rafInterval)
232
+ 使用 requestAnimationFrame 实现类似 setInterval 的效果。
233
+ 更适合动画、轮播、游戏循环等需要与浏览器帧同步的场景。
234
+
235
+ ```javascript
236
+ import { rafInterval } from 'jsly'
237
+
238
+ // 每秒执行一次
239
+ const stop = rafInterval(() => {
240
+ console.log('tick')
241
+ }, 1000)
242
+
243
+ // 停止执行
244
+ stop()
245
+ ```
246
+
247
+ 在 Vue 中使用
248
+
249
+ ```javascript
250
+ import { rafInterval } from 'jsly'
251
+ import { onMounted, onUnmounted } from 'vue'
252
+
253
+ let stop
254
+
255
+ onMounted(() => {
256
+ stop = rafInterval(() => {
257
+ console.log('frame update')
258
+ }, 16) // 约 60fps
259
+ })
260
+
261
+ onUnmounted(() => {
262
+ stop()
263
+ })
264
+ ```
265
+
266
+ ### 基轮询函数(poll)
267
+ 用于按固定间隔重复执行任务,直到:
268
+ - 返回结果为 truthy
269
+ - 超时
270
+ - 手动停止
271
+ 适用于接口状态查询、等待资源加载、检测条件成立等场景。
272
+
273
+ ```javascript
274
+ import { poll } from 'jsly'
275
+
276
+ // 轮询接口直到 ready === true
277
+ const { promise, stop } = poll(
278
+ async () => {
279
+ const res = await fetch('/api/status').then(r => r.json())
280
+ return res.ready && res
281
+ },
282
+ {
283
+ interval: 2000, // 每 2 秒执行一次
284
+ timeout: 10000 // 10 秒超时
285
+ }
286
+ )
287
+
288
+ promise
289
+ .then(data => {
290
+ console.log('成功:', data)
291
+ })
292
+ .catch(err => {
293
+ console.error('轮询失败:', err)
294
+ })
295
+
296
+ // 如需手动停止
297
+ // stop()
298
+ ```
package/dist/index.cjs.js CHANGED
@@ -7689,6 +7689,135 @@ function highlightKeyword(text, keyword, config = {
7689
7689
  return text.replace(pattern, match => `<${tag} style="${style}">${match}</${tag}>`);
7690
7690
  }
7691
7691
 
7692
+ /**
7693
+ * 基于 requestAnimationFrame 实现的定时器(RAF Interval)
7694
+ *
7695
+ * 功能类似于 setInterval,但内部使用 requestAnimationFrame 驱动。
7696
+ * 相比 setInterval:
7697
+ * - 与浏览器刷新率同步,更平滑
7698
+ * - 页面不可见时会自动暂停(节省性能)
7699
+ * - 不易出现掉帧或时间漂移问题
7700
+ *
7701
+ * 适用于动画、轮播、游戏循环等需要与渲染帧同步的场景。
7702
+ *
7703
+ * @template {(...args: any[]) => any} F
7704
+ * @param {F} callback - 每次间隔触发时执行的函数。
7705
+ * @param {number} delay - 执行间隔时间(毫秒)。
7706
+ * @returns {() => void} 返回一个停止函数,用于取消循环。
7707
+ *
7708
+ * @example
7709
+ * const stop = rafInterval(() => {
7710
+ * console.log('每秒执行一次')
7711
+ * }, 1000)
7712
+ *
7713
+ * // 停止
7714
+ * stop()
7715
+ *
7716
+ * @example
7717
+ * // 在 Vue 中使用
7718
+ * let stop
7719
+ * onMounted(() => {
7720
+ * stop = rafInterval(() => {
7721
+ * rotate.value += 1
7722
+ * }, 16)
7723
+ * })
7724
+ *
7725
+ * onUnmounted(() => {
7726
+ * stop()
7727
+ * })
7728
+ */
7729
+ function rafInterval(callback, delay) {
7730
+ if (typeof callback !== 'function') {
7731
+ throw new TypeError('rafInterval expected a function as the first argument');
7732
+ }
7733
+ let start = performance.now();
7734
+ let frameId = null;
7735
+ let stopped = false;
7736
+ function loop(now) {
7737
+ if (stopped) return;
7738
+ const delta = now - start;
7739
+ if (delta >= delay) {
7740
+ callback();
7741
+ // 使用 += 防止时间漂移
7742
+ start += delay;
7743
+ }
7744
+ frameId = requestAnimationFrame(loop);
7745
+ }
7746
+ frameId = requestAnimationFrame(loop);
7747
+ return function stop() {
7748
+ stopped = true;
7749
+ if (frameId) cancelAnimationFrame(frameId);
7750
+ };
7751
+ }
7752
+
7753
+ /**
7754
+ * 轮询函数(Polling)
7755
+ *
7756
+ * 按指定间隔重复执行异步任务,直到:
7757
+ * 1. 任务返回 truthy 值(成功)
7758
+ * 2. 超时
7759
+ * 3. 被手动停止
7760
+ *
7761
+ * 常用于:
7762
+ * - 接口状态查询
7763
+ * - 等待资源加载完成
7764
+ * - 检测条件成立
7765
+ *
7766
+ * @template T
7767
+ * @param {() => Promise<T> | T} task - 需要轮询执行的函数(支持同步或异步)。
7768
+ * @param {Object} [options]
7769
+ * @param {number} [options.interval=1000] - 每次轮询间隔时间(毫秒)。
7770
+ * @param {number} [options.timeout=0] - 超时时间(毫秒),0 表示不限制。
7771
+ * @returns {{ promise: Promise<T>, stop: () => void }}
7772
+ *
7773
+ * @example
7774
+ * const { promise, stop } = poll(
7775
+ * () => fetch('/api/status').then(r => r.json()),
7776
+ * { interval: 2000, timeout: 10000 }
7777
+ * )
7778
+ *
7779
+ * promise.then(res => console.log('成功:', res))
7780
+ */
7781
+ function poll(task, {
7782
+ interval = 1000,
7783
+ timeout = 0
7784
+ } = {}) {
7785
+ if (typeof task !== 'function') {
7786
+ throw new TypeError('poll expected a function as the first argument');
7787
+ }
7788
+ let stopped = false;
7789
+ let startTime = Date.now();
7790
+ let timer = null;
7791
+ const promise = new Promise((resolve, reject) => {
7792
+ async function execute() {
7793
+ if (stopped) return;
7794
+ try {
7795
+ const result = await task();
7796
+ if (result) {
7797
+ resolve(result);
7798
+ return;
7799
+ }
7800
+ if (timeout && Date.now() - startTime >= timeout) {
7801
+ reject(new Error('Polling timeout'));
7802
+ return;
7803
+ }
7804
+ timer = setTimeout(execute, interval);
7805
+ } catch (err) {
7806
+ reject(err);
7807
+ }
7808
+ }
7809
+ execute();
7810
+ });
7811
+ function stop() {
7812
+ stopped = true;
7813
+ if (timer) clearTimeout(timer);
7814
+ }
7815
+ return {
7816
+ promise,
7817
+ stop
7818
+ };
7819
+ }
7820
+
7692
7821
  exports.$bus = $bus;
7693
7822
  exports.buildObjFormData = buildObjFormData;
7694
7823
  exports.camelToSnake = camelToSnake;
@@ -7700,6 +7829,8 @@ exports.debounce = debounce;
7700
7829
  exports.deepDiff = deepDiff;
7701
7830
  exports.generateBrowserId = generateBrowserId;
7702
7831
  exports.highlightKeyword = highlightKeyword;
7832
+ exports.poll = poll;
7833
+ exports.rafInterval = rafInterval;
7703
7834
  exports.randomHash = randomHash;
7704
7835
  exports.sha256 = sha256;
7705
7836
  exports.shallowDiff = shallowDiff;