I'm brand new here, but I've read the guidelines and looked at some up-voted questions. Hopefully I understand how I'm supposed to do this... if not, please let me know and I'll fix my question.
Explanation
I have written a simple javascript plugin of sorts. It takes the settings object for a jQuery.ajax request and a few options (frequency, failure tolerance, etc) and then periodically issues the ajax request. If the request fails, it increases the interval to the next request. If success follows a number of failures, the interval is slowly reduced to reach the original frequency.
Usage looks something like this:
var ajax = {url: 'example.com', success: mySuccessFunc};
var cw = Coward(ajax, {}); // Request my url with default settings
cw.Start();
... // Coward is running, refreshes happening periodically
cw.Stop();
Purpose of Review
I'm open to any feedback about my code that you're willing to give me. However, I came here seeking a few things specifically:
- Are there any bugs that will result in an excessive number of requests or other undesirable behavior? Am I doing anything unsafe? Race conditions?
- JSLint warns me about scope in a few places, but the code works. Am I violating standards? Should I be handling scope differently (particularly with the 'underscore' functions and the 'live variables')?
The Code
// Coward - One time or periodic refresh that retreats if anything goes wrong.
//
// This tool was designed for use with jQuery 1.9 or later
//
// Written by Adam Jensen
function Coward(userReq, custom) {
'use strict';
//==============================================
// Options and variables used by the coward
// - Do not change `opts` after invocation -
//==============================================
// Combine user options with our defaults
var opts = $.extend({
frequency: 1000, // How often to issue ajax request
ceiling: 10, // After this number of failures, multiple will not be applied
multiple: 1.5, // Multiplier applied to
infinite: false, // If false, coward will stop running when ceiling is passed
verbose: false // If true, messages will appear in console log
}, custom);
// Live Variables (these will change as the code runs
// and be reset at restart)
var running = false;
var fails = 0;
var next = opts.frequency;
var nextId = "";
// Save out user's callbacks to be executed at the proper time
if (userReq.hasOwnProperty('success')) {
var successCallback = userReq.success;
delete userReq.success;
}
if (userReq.hasOwnProperty('error')) {
var errorCallback = userReq.error;
delete userReq.error;
}
// Combine user request sans callbacks with our default request
var request = $.extend({
url: 'localhost',
method: 'GET',
timeout: 3000,
success: _success,
error: _fail
}, userReq);
// Make sure we're not going to accidentally DoS someone
if (opts.multiple < 1) {
// multiple less than one results in increasing frequency on fail
_log("Multiple CANNOT be less than one!");
return false; // Lack of a callable will throw an error in users code
}
//==========================================================
// Main body of Coward (_run, _success, _fail)
// - DO NOT export these functions directly -
// - INSTEAD use Start, DelayStart, and Stop -
//==========================================================
// _run gives us a point to verify the status of the coward before the ajax call
function _run() {
if (!running) {
return false;
}
$.ajax(request);
return true;
}
// Our own callback used to check status and set next interval
function _success(data, status, jqxhr) {
// If there are failures, start to scale up requests again
if (fails > 0) {
fails--;
next = Math.round(next/opts.multiple);
_log("Success! Recovered a white flag, now have "+(opts.ceiling-fails)+". Next attempt in "+next);
}
// Set the next interval and call the users callback if set
if (next > 0) {
nextId = setTimeout(_run, next);
}
if (successCallback !== undefined) {
successCallback(data, status, jqxhr);
}
}
function _fail(jqxhr, status, error) {
// If we have fails left, log the incident and continue
if (fails < opts.ceiling) {
next = Math.round(next * opts.multiple);
_log('Refresh failed. '+(opts.ceiling-fails)+' white flags remaining. Retreating to '+next+' [front line at '+opts.frequency+'].');
fails++;
nextId = setTimeout(_run, next);
} else {
if (!opts.infinite) {
// We've passed the ceiling and infinite is not set. Give up.
_log('Refresh has failed more than '+fails+' times. I surrender!');
running = false;
} else {
_log('Failed more than '+fails+' times, ceiling '+opts.ceiling+' prevents increasing multiple.');
nextId = setTimeout(_run, next);
}
}
if (errorCallback !== undefined) {
errorCallback(jqxhr, status, error);
}
}
function _log(msg) {
if (opts.verbose) {
console.log(msg);
}
}
//===============================================
// "Publicly Accessible" Functions
//===============================================
var Start = function() {
// Reset vars in case of Stop/Start cycle
fails = 0;
next = opts.frequency;
nextId = "";
running = true;
return _run();
};
var DelayStart = function(delay) {
// Start after user provided delay or one "frequency cycle"
if (delay === undefined) {
setTimeout(Start, opts.frequency);
return opts.frequency;
}
setTimeout(Start, delay);
return delay;
};
var Stop = function() {
// Stop the run function from creating new ajax calls
clearTimeout(nextId);
running = false;
return false;
};
var Status = function() {
return {
Opts: opts,
Next: next,
Fails: fails,
NextId: nextId
};
};
return {
Start: Start,
DelayStart: DelayStart,
Stop: Stop,
Status: Status
};
}
The code is also available on github.