question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Performance Issue: setTimeout/setInterval

See original GitHub issue

It makes more sense to use setTimeout instead of setInterval.

If this is done, the function can determine when the text is actually going to change and not waste calls that don’t do anything. If the text says 2 years ago, it’s probably not going to change in a minute, so it makes more sense to calculate the time delay.

Also, this would be more efficient for a modified version that shows %d seconds ago and needs to update at 1000 ms intervals only before it becomes a minute. Then it can change the interval to 60000 ms.

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:5
  • Comments:7

github_iconTop GitHub Comments

1reaction
edwhcommented, Oct 1, 2016

Here’s a version (based on a slightly older base) which does this, and also doesn’t keep the timer running if the element is no longer in the DOM.

/*!
 * Timeago is a jQuery plugin that makes it easy to support automatically
 * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
 *
 * @name timeago
 * @version 1.4.0
 * @requires jQuery v1.2.3+
 * @author Ryan McGeary
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 *
 * For usage and examples, visit:
 * http://timeago.yarp.com/
 *
 * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
 * 
 * Modified by EH
 */

var timeAgoId = 1;

(function(factory){
    if(typeof define === 'function' && define.amd){
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    }else{
        // Browser globals
        factory(jQuery);
    }
}(function($){
    $.timeago = function(timestamp){
        if(timestamp instanceof Date){
            return inWords(timestamp);
        }else if(typeof timestamp === "string"){
            return inWords($.timeago.parse(timestamp));
        }else if(typeof timestamp === "number"){
            return inWords(new Date(timestamp));
        }else{
            return inWords($.timeago.datetime(timestamp));
        }
    };
    var $t = $.timeago;

    $.extend($.timeago, {
        settings: {
            refreshMillis: 60000,
            allowPast    : true,
            allowFuture  : false,
            localeTitle  : false,
            cutoff       : 0,
            strings      : {
                prefixAgo    : null,
                prefixFromNow: null,
                suffixAgo    : "ago",
                suffixFromNow: "from now",
                inPast       : 'any moment now',
                seconds      : "less than a minute",
                minute       : "about a minute",
                minutes      : "%d minutes",
                hour         : "about an hour",
                hours        : "about %d hours",
                day          : "a day",
                days         : "%d days",
                month        : "about a month",
                months       : "%d months",
                year         : "about a year",
                years        : "%d years",
                wordSeparator: " ",
                numbers      : []
            }
        },

        inWords: function(distanceMillis){
            if(!this.settings.allowPast && !this.settings.allowFuture){
                throw 'timeago allowPast and allowFuture settings can not both be set to false.';
            }

            var $l = this.settings.strings;
            var prefix = $l.prefixAgo;
            var suffix = $l.suffixAgo;
            if(this.settings.allowFuture){
                if(distanceMillis < 0){
                    prefix = $l.prefixFromNow;
                    suffix = $l.suffixFromNow;
                }
            }

            if(!this.settings.allowPast && distanceMillis >= 0){
                return this.settings.strings.inPast;
            }

            var seconds = Math.abs(distanceMillis) / 1000;
            var minutes = seconds / 60;
            var hours = minutes / 60;
            var days = hours / 24;
            var years = days / 365;

            function substitute(stringOrFunction, number){
                var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
                var value = ($l.numbers && $l.numbers[number]) || number;
                return string.replace(/%d/i, value);
            }

            var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
                seconds < 90 && substitute($l.minute, 1) ||
                minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
                minutes < 90 && substitute($l.hour, 1) ||
                hours < 24 && substitute($l.hours, Math.round(hours)) ||
                hours < 42 && substitute($l.day, 1) ||
                days < 30 && substitute($l.days, Math.round(days)) ||
                days < 45 && substitute($l.month, 1) ||
                days < 365 && substitute($l.months, Math.round(days / 30)) ||
                years < 1.5 && substitute($l.year, 1) ||
                substitute($l.years, Math.round(years));

            var separator = $l.wordSeparator || "";
            if($l.wordSeparator === undefined){
                separator = " ";
            }
            return $.trim([prefix, words, suffix].join(separator));
        },

        parse   : function(iso8601){
            var s = $.trim(iso8601);
            s = s.replace(/\.\d+/, ""); // remove milliseconds
            s = s.replace(/-/, "/").replace(/-/, "/");
            s = s.replace(/T/, " ").replace(/Z/, " UTC");
            s = s.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); // -04:00 -> -0400
            s = s.replace(/([\+\-]\d\d)$/, " $100"); // +09 -> +0900
            return new Date(s);
        },
        datetime: function(elem){
            var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
            return $t.parse(iso8601);
        },
        isTime  : function(elem){
            // jQuery's `is()` doesn't play well with HTML5 in IE
            return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
        }
    });

    // functions that can be called via $(el).timeago('action')
    // init is default when no action is given
    // functions are called with context of a single element
    var functions = {
        init         : function(){
            this.id = timeAgoId++;
            var refresh_el = $.proxy(refresh, this);
            refresh_el(true);
        },
        update       : function(time){
            var parsedTime = $t.parse(time);
            $(this).data('timeago', { datetime: parsedTime });
            if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString());
            refresh.apply(this);
        },
        updateFromDOM: function(){
            $(this).data('timeago', { datetime: $t.parse($t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title")) });
            refresh.apply(this);
        },
        dispose      : function(){
            if(this._timeagoInterval){
                window.clearInterval(this._timeagoInterval);
                this._timeagoInterval = null;
            }
        }
    };

    $.fn.timeago = function(action, options){
        var fn = action ? functions[action] : functions.init;
        if(!fn){
            throw new Error("Unknown function name '" + action + "' for timeago");
        }
        // each over objects here and call the requested function
        this.each(function(){
            fn.call(this, options);
        });
        return this;
    };

    function refresh(first){
        if (first || $(this).closest('body').length) {
            var data = prepareData(this);
            var $s = $t.settings;
            var nextTime = $s.refreshMillis;

            if(!isNaN(data.datetime)){
                var dist = distance(data.datetime);

                if($s.cutoff == 0 || dist < $s.cutoff){
                    $(this).text(inWords(data.datetime));
                }

                var seconds = Math.abs(dist) / 1000;
                var minutes = seconds / 60;
                var hours = minutes / 60;
                var days = hours / 24;
                var years = days / 365;

                // If the next change is a long time away, set the timer appropriately.
                if (years > 1) {
                    nextTime = 365 * 24 * 60 * 60 * 1000 / 2;
                } else if (days > 1) {
                    nextTime = 24 * 60 * 60 * 1000 / 2;
                } else if (hours > 1) {
                    nextTime = 60 * 60 * 1000 / 2;
                } else if (minutes > 1) {
                    nextTime = 60 * 1000 / 2;
                } 
            }

            var refresh_el = $.proxy(refresh, this);
            var $s = $t.settings;
            if($s.refreshMillis > 0){
                this._timeagoInterval = setTimeout(refresh_el, nextTime);
            }
        }

        return this;
    }

    function prepareData(element){
        element = $(element);
        if(!element.data("timeago")){
            element.data("timeago", { datetime: $t.datetime(element) });
            var text = $.trim(element.text());
            if($t.settings.localeTitle){
                element.attr("title", element.data('timeago').datetime.toLocaleString());
            }else if(text.length > 0 && !($t.isTime(element) && element.attr("title"))){
                element.attr("title", text);
            }
        }
        return element.data("timeago");
    }

    function inWords(date){
        return $t.inWords(distance(date));
    }

    function distance(date){
        return (new Date().getTime() - date.getTime());
    }

    // fix for IE6 suckage
    document.createElement("abbr");
    document.createElement("time");
}));
0reactions
theonlypwnercommented, Aug 17, 2017

Unless you can tolerate error of up to 1 year, disregard what I said about grouping timers, as 2 years ago for one timer could be supposed to update a few second after another timer that says the same. Updating all of the “year” timers at the same time could be more efficient (less timers), but less accurate.


The timeago.org plugin updates the timer 1 second after immediately when it’s supposed to change. It makes sense to set the timer like this (but maybe 1 ms after the time instead of 1 entire second).

  /**
   * nextInterval: calculate the next interval time.
   * - diff: the diff sec between now and date to be formated.
   *
   * What's the meaning?
   * diff = 61 then return 59
   * diff = 3601 (an hour + 1 second), then return 3599
   * make the interval with high performace.
  **/
  function nextInterval(diff) {
    var rst = 1, i = 0, d = Math.abs(diff);
    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
      diff /= SEC_ARRAY[i];
      rst *= SEC_ARRAY[i];
    }
    // return leftSec(d, rst);
    d = d % rst;
    d = d ? rst - d : rst;
    return Math.ceil(d);
  }

https://github.com/hustcc/timeago.js/blob/310864d574c89cb692c0395dd7fe650cfa12fba7/src/timeago.js#L80-L99

Read more comments on GitHub >

github_iconTop Results From Across the Web

does setTimeout() affects performance - Stack Overflow
No significant effect at all, setTimeout runs in an event loop, it doesn't block or harm execution. – Benjamin Gruenbaum. Oct 24, 2013...
Read more >
Angular — Performance issue caused by setTimeout() and ...
From my experience, one of the most easily missed causes of the second one is timer, which means setTimeout() and setInterval() . Too...
Read more >
Scheduling: setTimeout and setInterval
setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.
Read more >
The curious case of performance testing setTimeout(0)
setInterval runs the function repeatedly, waiting 50 ms between each run, to make sure the stack is clear. This is inelegant, but tests...
Read more >
setTimeout() - Web APIs | MDN
Timeouts are cancelled using clearTimeout() . To call a function repeatedly (e.g., every N milliseconds), consider using setInterval() . Non- ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found