简体   繁体   中英

Trying to understand DRY principles in Javascript

I am currently trying to sharpen my refactoring skills, I have a block of code that I have written that has two methods that are very similar, and I am trying to wrap my head around simplifying my bloated code, any suggestions would be welcome.

As you can see the two methods are very similar, and the only real difference is the URL the POST to.

authenticateA : function( e ) {
  var $this = $( e.target ).closest( '[data-fn]' )
  ,   text = $this.text()
  ,   that = this;

  $this.text( 'Authenticating...' ).addClass("auth-button-disable")

  $.ajax({
    type : 'POST',
    url : '/A_authentications/update/',
    data : { _method : 'PUT', sms_token : this.$el.find( '#sms-input' ).val() },
    complete: function( xhr ) {

      if ( xhr.status === 200 )
        that.relocate();
      else {
        $this.text( text ).removeClass("auth-button-disable");
        that.handleError( xhr.status );
      }
    },
    dataType : 'json'
  });
},

authenticateB : function( e ) {
  var $this = $( e.target ).closest( '[data-fn]' )
  ,   text = $this.text()
  ,   that = this;

  $this.text( 'Authenticating...' ).addClass("auth-button-disable")

  $.ajax({
    type : 'POST',
    url : '/B_authentications/',
    data : { otp : this.$el.find( '#B-input' ).val() },
    complete: function( xhr ) {
      if ( xhr.status === 200 )
        that.relocate();
      else {
        $this.text( text ).removeClass("auth-button-disable");
        that.handleError( xhr.status )
      }
    },
    dataType : 'json'
  });
}

I call the methods as click functions in an events block:

'click [data-fn="authenticate-A"]' : 'authenticateA',
'click [data-fn="authenticate-B"]' : 'authenticateB'

I think these could be refactored into one method or two slimmer methods, I'm just not sure where to start, thanks again in advance.

You can have a function that generates those functions:

generateAuthFunction : function( authDetails) {
  return function (e) {
    var $this = $( e.target ).closest( '[data-fn]' )
    ,   text = $this.text()
    ,   that = this;

    $this.text( 'Authenticating...' ).addClass("auth-button-disable")

    $.ajax({
      type : 'POST',
      url : authDetails.url,
      data : authDetails.dataFunc(this),
      complete: function( xhr ) {

        if ( xhr.status === 200 )
          that.relocate();
        else {
          $this.text( text ).removeClass("auth-button-disable");
          that.handleError( xhr.status );
        }
      },
      dataType : 'json'
    });
  };
}

Then you generate it with:

var authDetailsA = {
  url :  '/A_authentications/update/',
  dataFunc : function (this) {
    return { _method : 'PUT', sms_token : this.$el.find( '#sms-input' ).val() };
  }
};
var authDetailsB = {
  url :  '/B_authentications/',
  dataFunc : function (this) {
    return { otp : this.$el.find( '#B-input' ).val() };
};
authenticateA : generateAuthFunction(authDetailsA);
authenticateB : generateAuthFunction(authDetailsB);

You could call it like before:

'click [data-fn="authenticate-A"]' : 'authenticateA',
'click [data-fn="authenticate-B"]' : 'authenticateB'

I think this might even introduce unnecessary complexity, but it is more DRY.

  1. Abstract away your requests. Mixing your app logic into your views can only muddle the picture. Let's create an Authentication module:

     var Authentication = (function(Backbone, _) { function whoGoesThere(opts) { opts = _.extend({}, opts, { type : 'POST', dataType: 'json' }); return Backbone.$.ajax(opts); } return { A: function(data) { data = _.extend({}, data, { _method : 'PUT' }); return whoGoesThere({ url : '/A_authentications/update/', data: data }); }, B: function(data) { return whoGoesThere({ url : '/B_authentications/', data: data }); } }; })(Backbone, _); 
  2. Configure your views to handle the events with a function instead of a function name, pass your values to the appropriate method on the previous module and then delegate the returned promise to a common handler:

     events: { 'click [data-fn="authenticate-A"]': function(e) { var promise = Authentication.A({ sms_token : this.$el.find( '#sms-input' ).val() }); this.onAuthentication(e, promise); }, 'click [data-fn="authenticate-B"]': function(e) { var promise = Authentication.B({ otp : this.$el.find( '#B-input' ).val() }); this.onAuthentication(e, promise); } } 
  3. Handle the promise (here the Ajax object but that could be anything)

     onAuthentication: function(e, promise) { var $this = $(e.target).closest('[data-fn]') , text = $this.text() , that = this; $this.text( 'Authenticating...' ).addClass("auth-button-disable"); promise.done(function() { that.relocate(); }); promise.fail(function(xhr) { that.handleError(xhr.status); }); promise.always(function() { $this.text(text).removeClass("auth-button-disable"); }); } 

And a demo http://jsfiddle.net/s3ydy3u6/1/

Try (untested code):

authenticate: function(t, e) { // t = 'A' || 'B'
  var $this = $( e.target ).closest( '[data-fn]' )
  ,   text = $this.text()
  ,   that = this
  ,   url
  ,   data;

  $this.text( 'Authenticating...' ).addClass("auth-button-disable")

  // conditionnaly set up your variables
  if(t == 'A') {
      data = { _method : 'PUT', sms_token : this.$el.find( '#sms-input' ).val() }; 
      url = '/A_authentications/update/';
  } else if(t == 'B') {
      url = '/B_authentications/';
      data = { otp : this.$el.find( '#B-input' ).val() };
  }

  $.ajax({
    type : 'POST',
    url : url, // use them
    data : data, // use them
    complete: function( xhr ) {

      if ( xhr.status === 200 )
        that.relocate();
      else {
        $this.text( text ).removeClass("auth-button-disable");
        that.handleError( xhr.status );
      }
    },
    dataType : 'json'
  });
},

You can just check the data-fn attribute in the authenticate function.

authenticate: function (e) {        
        var $this = $(e.target).closest('[data-fn]'),
            text = $this.text(),
            that = this;
        $this.text('Authenticating...').addClass("auth-button-disable");
        var fn = $this.data("fn");

        switch (fn) {
            case "authenticate-A":
                data = {
                    _method: 'PUT',
                    sms_token: this.$el.find('#sms-input').val()
                };
                url = '/A_authentications/update/';
                break;
            case "authenticate-B":
                data = {
                    otp: this.$el.find('#B-input').val()
                };
                url = '/B_authentications/update/';
                break;

        }

        $.ajax({
            type: 'POST',
            url: url,
            data: data,
            complete: function (xhr) {

                if (xhr.status === 200) that.relocate();
                else {
                    $this.text(text).removeClass("auth-button-disable");
                    that.handleError(xhr.status);
                }
            },
            dataType: 'json'
        });


    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM