/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ var ORIGIN_METHOD = '\0__throttleOriginMethod' var RATE = '\0__throttleRate' var THROTTLE_TYPE = '\0__throttleType' /** * @public * @param {(Function)} fn * @param {number} [delay=0] Unit: ms. * @param {boolean} [debounce=false] * true: If call interval less than `delay`, only the last call works. * false: If call interval less than `delay, call works on fixed rate. * @return {(Function)} throttled fn. */ export function throttle(fn, delay, debounce) { var currCall var lastCall = 0 var lastExec = 0 var timer = null var diff var scope var args var debounceNextCall delay = delay || 0 function exec() { lastExec = (new Date()).getTime() timer = null fn.apply(scope, args || []) } var cb = function() { currCall = (new Date()).getTime() scope = this args = arguments var thisDelay = debounceNextCall || delay var thisDebounce = debounceNextCall || debounce debounceNextCall = null diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay clearTimeout(timer) // Here we should make sure that: the `exec` SHOULD NOT be called later // than a new call of `cb`, that is, preserving the command order. Consider // calculating "scale rate" when roaming as an example. When a call of `cb` // happens, either the `exec` is called dierectly, or the call is delayed. // But the delayed call should never be later than next call of `cb`. Under // this assurance, we can simply update view state each time `dispatchAction` // triggered by user roaming, but not need to add extra code to avoid the // state being "rolled-back". if (thisDebounce) { timer = setTimeout(exec, thisDelay) } else { if (diff >= 0) { exec() } else { timer = setTimeout(exec, -diff) } } lastCall = currCall } /** * Clear throttle. * @public */ cb.clear = function() { if (timer) { clearTimeout(timer) timer = null } } /** * Enable debounce once. */ cb.debounceNextCall = function(debounceDelay) { debounceNextCall = debounceDelay } return cb } /** * Create throttle method or update throttle rate. * * @example * ComponentView.prototype.render = function () { * ... * throttle.createOrUpdate( * this, * '_dispatchAction', * this.model.get('throttle'), * 'fixRate' * ); * }; * ComponentView.prototype.remove = function () { * throttle.clear(this, '_dispatchAction'); * }; * ComponentView.prototype.dispose = function () { * throttle.clear(this, '_dispatchAction'); * }; * * @public * @param {Object} obj * @param {string} fnAttr * @param {number} [rate] * @param {string} [throttleType='fixRate'] 'fixRate' or 'debounce' * @return {Function} throttled function. */ export function createOrUpdate(obj, fnAttr, rate, throttleType) { var fn = obj[fnAttr] if (!fn) { return } var originFn = fn[ORIGIN_METHOD] || fn var lastThrottleType = fn[THROTTLE_TYPE] var lastRate = fn[RATE] if (lastRate !== rate || lastThrottleType !== throttleType) { if (rate == null || !throttleType) { return (obj[fnAttr] = originFn) } fn = obj[fnAttr] = throttle( originFn, rate, throttleType === 'debounce' ) fn[ORIGIN_METHOD] = originFn fn[THROTTLE_TYPE] = throttleType fn[RATE] = rate } return fn } /** * Clear throttle. Example see throttle.createOrUpdate. * * @public * @param {Object} obj * @param {string} fnAttr */ export function clear(obj, fnAttr) { var fn = obj[fnAttr] if (fn && fn[ORIGIN_METHOD]) { obj[fnAttr] = fn[ORIGIN_METHOD] } }