【源码共读】| delay 带取消功能的延迟函数

733 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

【若川视野 x 源码共读】第18期 | delay 带取消功能的延迟函数 点击了解本期详情一起参与

今天阅读的是:delay

image-20221130103344369

字面理解,这个库的作用是做延迟执行的

我们先看看测试用例

// index.test-d.ts
// 延迟 200ms
expectType<ClearablePromise<void>>(delay(200));
// 延迟 200ms,支持输入string
expectType<ClearablePromise<string>>(delay(200, { value: "🦄" }));
// 延迟 200ms,支持输入number
expectType<ClearablePromise<number>>(delay(200, { value: 0 }));
// 支持取消执行
expectType<ClearablePromise<void>>(
    delay(200, { signal: new AbortController().signal })
);
// 支持随机延迟时间
expectType<ClearablePromise<number>>(delay.range(50, 200, { value: 0 }));
// 支持异常处理
expectType<ClearablePromise<never>>(delay.reject(200, { value: "🦄" }));
expectType<ClearablePromise<never>>(delay.reject(200, { value: 0 }));

// 自定义定时器
const customDelay = delay.createWithTimers({ clearTimeout, setTimeout });
expectType<ClearablePromise<void>>(customDelay(200));

expectType<ClearablePromise<string>>(customDelay(200, { value: "🦄" }));
expectType<ClearablePromise<number>>(customDelay(200, { value: 0 }));

expectType<ClearablePromise<never>>(customDelay.reject(200, { value: "🦄" }));
expectType<ClearablePromise<never>>(customDelay.reject(200, { value: 0 }));

const unrefDelay = delay.createWithTimers({
    clearTimeout,
    setTimeout(...args) {
        return setTimeout(...args).unref();
    },
});

这个库是使用Promise + setTimeout实现的

总结一下,具体需要实现的功能点:

  • 支持传入延时时间
  • 传递value参数作为结果导出
    • 可以输入数字
    • 可以输入字符串
  • 支持取消功能
  • 支持随机延时时间
  • 能够失败返回
  • 支持传入自定义定时器

源码分析

"use strict";

// From https://github.com/sindresorhus/random-int/blob/c37741b56f76b9160b0b63dae4e9c64875128146/index.js#L13-L15
const randomInteger = (minimum, maximum) =>
// 边界控制,取整
Math.floor(Math.random() * (maximum - minimum + 1) + minimum);

const createAbortError = () => {
    const error = new Error("Delay aborted");
    error.name = "AbortError";
    return error;
};

const createDelay =
      ({ clearTimeout: defaultClear, setTimeout: set, willResolve }) =>
(ms, { value, signal } = {}) => {
    // 检查是否传入Abort Control
    // 这个跟axios中的很类似
    // https://axios-http.com/docs/cancellation
    // https://developer.mozilla.org/en-US/docs/Web/API/AbortController
    if (signal && signal.aborted) {
        return Promise.reject(createAbortError());
    }

    let timeoutId;
    let settle;
    let rejectFn;
    const clear = defaultClear || clearTimeout;
// 取消时  重置并返回结果
    const signalListener = () => {
        clear(timeoutId);
        rejectFn(createAbortError());
    };
// 清楚abort事件
    const cleanup = () => {
        if (signal) {
            signal.removeEventListener("abort", signalListener);
        }
    };

    const delayPromise = new Promise((resolve, reject) => {
        settle = () => {
            cleanup();
            // 根据传入willResolve,决定成功返回还是失败返回
            if (willResolve) {
                resolve(value);
            } else {
                reject(value);
            }
        };

        rejectFn = reject;
        // 判断是否传入自定义setTimeout
        // 如果没有传入,则根据传入的 ms 创建
        timeoutId = (set || setTimeout)(settle, ms);
    });

    if (signal) {
        // signal.abort()  取消请求
        signal.addEventListener("abort", signalListener, { once: true });
    }
    // 重置
    delayPromise.clear = () => {
        clear(timeoutId);
        timeoutId = null;
        settle();
    };

    return delayPromise;
};
// 自定义定时器
const createWithTimers = (clearAndSet) => {
    const delay = createDelay({ ...clearAndSet, willResolve: true });
    delay.reject = createDelay({ ...clearAndSet, willResolve: false });
    delay.range = (minimum, maximum, options) =>
    delay(randomInteger(minimum, maximum), options);
    return delay;
};

const delay = createWithTimers();
delay.createWithTimers = createWithTimers;

module.exports = delay;
// TODO: Remove this for the next major release
module.exports.default = delay;


总结

通过对上述源码分析,我们可以得知以下几点:

  • 通过在Promise中设定setTimeout来实现延时执行的功能
  • 使用了Abort Control来实现取消的功能
  • 通过Math.floor()向下取整

相关链接