4
\$\begingroup\$

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.

asked Oct 5, 2015 at 21:01
\$\endgroup\$
3
  • \$\begingroup\$ Fantastic first question. This is exemplary. Welcome to Codereview. \$\endgroup\$ Commented Oct 5, 2015 at 21:17
  • \$\begingroup\$ "Combine user request sans callbacks with our default request" - I believe your code is broken there. \$\endgroup\$ Commented Oct 7, 2015 at 0:37
  • \$\begingroup\$ @JosephtheDreamer I appreciate you taking a look. Could you clarify what you mean when you say the code is broken? Are you seeing unexpected behavior in your testing or did some mistake catch your eye? \$\endgroup\$ Commented Oct 7, 2015 at 4:38

1 Answer 1

3
+50
\$\begingroup\$

You should check that the supplied success and error callbacks are functions not just that they exist.

if (typeof userReq.success === "function") {
 var successCallback = userReq.success;
 delete userReq.success;
}

The way you have it now, if a user uses the following configs :

var ajax = {url: 'example.com', success: null};
var ajax = {url: 'example.com', success: "my success function"};

will cause the check below to pass, resulting in an error

if (successCallback !== undefined) {
 successCallback(data, status, jqxhr);
}
answered Oct 9, 2015 at 16:20
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.