一个可配置是否添加后立即执行、延迟执行时间(ms)、准时执行时间(ms)、持续执行时间(ms)、是否自动启动、是否循环执行、自动移除后回调的定时器想要么?
/**
* 获取参数原始类型
*/
const _toString = Object.prototype.toString;
function toRawType(value) {
return _toString.call(value).slice(8, -1);
}
/**
* 是否是函数
*/
function isFunc(func) {
return toRawType(func) === 'Function';
}
/**
* 空函数
*/
function noop() {}
let key = 0;
/**
* 格式数字为整千位
* @param {*} num
* @param {*} ratio
*/
const formatNum = (num, ratio) => {
return Math.floor(Math.abs(num) / ratio) * ratio;
};
/**
* 全局定时器
* @param {Number} timeout 超时时间
*/
export default class {
constructor(timeout = 1000) {
this.timeout = timeout;
this.init();
}
/**
* 初始化
*/
init() {
// 记录状态
this.timerID = null;
// 执行队列
this.timers = [];
// 计数器
this.count = 0;
// 起始时间
this.startTime = 0;
}
/**
* 添加任务
* @param {Function} fn 待执行函数
* @param {Object} opts 配置项
* @param {Number} opts.immediately 是否添加后立即执行
* @param {Number} opts.delayTime 延迟执行时间(ms)
* @param {Number} opts.triggerTime 准时执行时间(ms)
* @param {Number} opts.duration 持续执行时间(ms)
* @param {Boolean} opts.autoStart 是否自动启动
* @param {Boolean} opts.loop 是否循环执行
* @param {Function} opts.removed 自动移除后回调
* @param {Function} opts.timeout 间隔时间(s)
* @param {Array<Object>} opts.durationFns 倒计时时间节点回调
* @param {Boolean} {desc} 是否倒序
* @param {Number} {time} 时间点(ms)
* @param {Function} {fn} 回调
* @return {Number} 任务id
*
* @description 执行函数返回参数:duration:剩余执行时间,runTime:已执行时间
*/
add(fn, opts = {}) {
// 验证是否为函数
if (!isFunc(fn)) {
throw new Error('callback must be Function!');
}
opts = {
autoStart: true, // 默认自动启动
immediately: false, // 默认不立即执行
duration: -1, // 默认持续时间无穷
durationFns: [], // 默认无时间节点回调
delayTime: 0, // 默认不延迟执行
triggerTime: 0, // 默认不准时执行
timeout: 1, // 默认每秒执行一次
removed: noop, // 默认移除回调
loop: !opts.triggerTime, // 若有准时执行时间则默认不循环,否则循环
...opts,
fn, // 避免配置中重名属性覆盖
key: ++key, // 任务id
runTime: 0 // 任务已执行时长
};
const { autoStart, immediately, duration, runTime } = opts;
// 若设置立即执行则先一步执行该函数
immediately && fn({ duration, runTime });
this.timers.push(opts);
autoStart && this.start();
return key;
}
/**
* 移除任务
* @param {Number} keys 任务id,可传多个
*/
remove(...keys) {
let ret = 0;
keys.forEach(id => {
const index = this.timers.findIndex(({ key }) => key === id);
// 找到则删除任务
if (index > -1) {
this.timers.splice(index, 1);
ret++;
}
});
return ret === keys.length;
}
/**
* 开启定时器
*/
start() {
// 仅在停止状态才会启动,避免启动多个定时器
if (!this.timerID) {
// 设置启动时间
this.startTime = Date.now();
const _this = this;
(function runNext() {
// 获取任务数量
const len = _this.timers.length;
// 只有timers中有待处理函数才会继续执行
if (len > 0) {
// 记录执行时间
const now = Date.now();
for (let i = 0; i < len; i++) {
const { fn, delayTime, triggerTime, loop, duration, runTime, removed, durationFns, timeout } = _this.timers[i];
const copyItem = {};
/**
* 移除该任务
*/
const removeItem = () => {
_this.timers.splice(i, 1);
// 移除后触发回调
isFunc(removed) && removed();
};
// 时间间隔相余后为0 && 延时结束 && (无准时执行时间 || 准时执行时间小于当前时间) && 持续时间内
if (!(_this.count % timeout) && delayTime <= 0 && (!triggerTime || triggerTime < now) && duration !== 0) {
// 剩余时间(ms)
const leftTime = formatNum(duration, _this.timeout);
fn({ duration: leftTime, runTime });
// 计算运行时间
copyItem.runTime = _this.timeout * (_this.count + timeout);
// 执行时间节点回调
copyItem.durationFns = durationFns.map((item) => {
const { time, desc = false, fn = noop, hasExecute = false } = item;
// ((逆向计时 && 剩余时间小于等于设定时间) || (正向计时 && 已执行时间大于等于设定时间)) && 未执行过
if (((desc && time >= leftTime) || (!desc && runTime >= time)) && !hasExecute) {
// 时间符合条件
// 未执行过,则执行一次
fn({ duration: time });
// 标记为已执行
item.hasExecute = true;
return {
...item,
hasExecute: true
};
} else {
return item;
}
});
// 若loop为false则在执行一次后移除该方法移除该任务
!loop && removeItem();
} else if (duration === 0) {
// 若剩余时间为零移除该任务
removeItem();
}
// 减少delay时间
if (delayTime > 0) {
copyItem.delayTime = delayTime - _this.timeout;
} else if (duration > 0) {
// 减少持续时间
copyItem.duration = formatNum(duration - _this.timeout, _this.timeout);
}
// 更新任务配置
_this.timers[i] = {
..._this.timers[i],
...copyItem
};
}
// 重置时间计算包含队列偏移量
const afterNow = Date.now();
// 计算偏移量
const offset = afterNow - (_this.startTime + _this.count++ * _this.timeout);
// 下次执行时间
const nextTime = (_this.timeout - offset) < 0 ? 0 : _this.timeout - offset;
_this.timerID = setTimeout(runNext, nextTime);
} else {
_this.stop();
}
})();
}
}
/**
* 停止定时器
*/
stop() {
clearTimeout(this.timerID);
this.init();
}
}
Comments
注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。