Coder Social home page Coder Social logo

promiseadventure's Introduction

A Promise Adventure

I have been working on the plugin-cybersource repository to get it working for Voids, Credits and Debits with the new Soap Integration. A year ago, Kev wrote the code to perform a transaction reversal, and I have adapted this code to do the new actions.

The way Kev wrote the inegration was to new up a separate object each time the code was called (which would allow us to hotswap the authorisation keys each time without having to start the Works).

When I first adapted the code I copied and pasted the code 4 times, until I was sure I was generating the right SOAP payload and then I turned my attention to breaking the scripted code into promise-chained functions. However, I started to encounter issues with the value of 'this', which I have recreated in Chapter 1 below. From working through this issue I think I may have come up with a way to separate things like the Works controllers into separately testable chunks.

This 'adventure' is abstracting the ideas away from our current implementation so we can have a conversation about what is the best way to go.

Please clone this repo and do a npm install.

Prologue - The Works controller-like function

Run

node prologue.js

and then take a look at this code

var Q = require( 'q' );

var promiseChain = function(initialValue) {
  var value1 = initialValue;
  var value2;
  var value3;

  function firstFunction( ) {
    console.log('First Function (Works Controller)');
    console.log('Initial Value', value1);
    value2 = 'abc';
    return Q.resolve();
  }

  function secondFunction( ) {
    console.log('Second Function (Works Controller)');
    console.log('Initial Value', value1);

    value3 = 'def';
    return Q.resolve();
  }

  function thirdFunction( ) {
    console.log('Third function (Works Controller)');
    console.log('Initial Value', value1);
    return Q.resolve( {
      value1: value1,
      value2: value2,
      value3: value3
   } );
  }

  return firstFunction()
  .then(secondFunction)
  .then(thirdFunction)
}

module.exports = {
  promiseChain: promiseChain
}

This is then called by

var worksController = require('./worksController');

worksController.promiseChain('123');

In a Works controller we basically declare a parent function with a load of child functions that are then chained together using promises.

As the child functions are within the scope of the parent they gain access to the variables declared in the parent scope allowing us to store values early on and then access them later (instead of having to pass through a lot of unrelated objects with each resolve).

Unfortunately, this means it is impossible to test each child function as a unit.

Chapter 1 - Using a separate object

Run

node chapter1.js

and then take a look at this code

var Q = require( 'q' );

PromiseAdventure = function() {

}

PromiseAdventure.prototype.promiseChain = function(initialValue) {
  return this.firstFunction( initialValue )
  .then(this.secondFunction)
  .then(this.thirdFunction)
}

PromiseAdventure.prototype.firstFunction = function(initialValue) {
  this.value1 = initialValue; 
  console.log('First Function (Separate Object Losing This)');
  console.log('Value 1', this.value1);
  console.log('Value 2', this.value2);
  console.log('Value 3', this.value3);
  return Q.resolve();
}

PromiseAdventure.prototype.secondFunction = function() {
  this.value2 = 'abc';
  console.log('Second Function (Separate Object Losing This)');
  console.log('Value 1', this.value1);
  console.log('Value 2', this.value2);
  console.log('Value 3', this.value3);
  
  return Q.resolve();
}

PromiseAdventure.prototype.thirdFunction = function() {
  this.value3 = 'def';
  console.log('Third function (Separate Object Losing This)');
  console.log('Value 1', this.value1);
  console.log('Value 2', this.value2);
  console.log('Value 3', this.value3);
  return Q.resolve( {
    value1: this.value1,
    value2: this.value2,
    value3: this.value3
 } );
}

module.exports = PromiseAdventure;

This is then called by

var SeparateObjectLosingThis = require('./separateObjectLosingThis');

var separateObjectLosingThis = new SeparateObjectLosingThis();

return separateObjectLosingThis.promiseChain('123');

This is where I got to initially when I was breaking apart the plugin-cybersource code to be a promise chain with separate functions.

I was very quickly losing the value held in value1.

However, I did quite like how everything was broken down - things felt a bit more like a Classical OO language, and it feels like the direction that Es6 is leaning as well https://github.com/addyosmani/es6-equivalents-in-es5 - look at the classes section.

Chapter 2 - Separate Object - Passing Around Self/This

Run

node chapter2.js

Then read this code (just a fragment this time)

PromiseAdventure.prototype.promiseChain = function(initialValue) {
  
  console.log('Promise Chain (Separate Object Passing This)');
  console.log('Value 1', this.value1);
  console.log('Value 2', this.value2);
  console.log('Value 3', this.value3);

  return this.firstFunction( this, initialValue )
  .then(this.secondFunction)
  .then(this.thirdFunction)
}

PromiseAdventure.prototype.firstFunction = function(self, initialValue ) {
  this.value1 = initialValue;
  
  console.log('First Function (Separate Object Passing This)');
  console.log('Value 1', self.value1);
  console.log('Value 2', self.value2);
  console.log('Value 3', self.value3);
  return Q.resolve( self );
}

This is where I am currently at with the plugin-cybersource work. My simple solution to the problem above is to just pass this into the promise chain, renaming it to self in the parameter, and then resolving with that value each time.

My thought for if there was a sequence of a Revolver request where we prepare, send and process a request (so you couldn't use this pattern) that I would just put them in a sub chain - as that could be seen as a complete unit.

For me, this feels like it will be unit testable (one of my next challenges), and feels tidier than some of the other things we have played with.

Chapter 3 - Separate Object - Binding This

Run

node chapter3.js

Then read this code (fragment)

PromiseAdventure.prototype.promiseChain = function( initialValue ) {
  // Can't seem to add anything to this here!!!

  return this.firstFunction( initialValue )
  .then(this.secondFunction)
  .then(this.thirdFunction)
}

PromiseAdventure.prototype.firstFunction = function( initialValue ) {
  this.value1 = initialValue;
  
  console.log('First Function (Separate Object Binding This)');
  console.log('Value 1', this.value1);
  console.log('Value 2', this.value2);
  console.log('Value 3', this.value3);
  return Q.resolve();
}.bind(this)

I showed the passing around this to Kev last week and he suggested maybe looking at using Javascript binding so tonight I have hacked something together.

I must admit my ignorance of bind/call/apply (I have some funny gaps in my JS knowledge) but this also seems to work in this simple case.

In the promise chain function I can't seem to add things to this that is then picked up by the child functions but probably doing something silly.

Epilogue

Please tell me what you think. Does this feel like the right route to go down? Do you prefer Chapter 3 to Chapter 2?

Maybe use the github issues for feedback

promiseadventure's People

Contributors

philmeehan avatar

Watchers

 avatar James Cloos avatar

Forkers

robhuzzey

promiseadventure's Issues

Bind & Call

Hey,

I commented on Kev's issue already but I wanted to highlight a thing I noticed in the code that didn't make sense to me:
https://github.com/philmeehan/promiseAdventure/blob/master/separateObjectBindingThis.js#L24

Usually you would use bind on the invocation of the function rather than the definition... on that line this points to the module... is that expected behaviour?

I must admit my ignorance of bind/call/apply

This helps me:
bind - returns a new function with the scope shifted to the scope passed in.
call - calls the function with the scope passed in
apply - same as call except you have to pass an array of arguments.

I would be very happy to sit with you & discuss all these things, maybe even just to bounce ideas off?

I hope that helps :)

Thoughts on chapter 3

I've done fair bit of playing around with this stuff and you have hit the nail on the head here, scope across promises is an anti-pattern. There's 2 issues I have with the chapter 3 solution.

  1. We can't tell what parameters the functions are expecting nor what data they should return
  2. Each function in the chain has the ability to amend any property of this at any stage, i'm gonna call this scope bleed. If someone were to come in and alter the behaviour of PromiseAdventure._secondFunction, say we make it affect an object that PromiseAdventure._thirdFunction relies on, there is no clear indication that we're breaking the expectations of PromiseAdventure._thirdFunction.

Something like the following would remove those issues and firm up/make more visible the contract between functions but gets pretty messy.

var PromiseAdventure = module.exports = {};

var _ = require( 'lodash' );

PromiseAdventure._orchestrate = function( request ) {
    // aggregate the responses
    var data = {
        request: request
    };

    // only pass in the variables the function will need
    var firstFunctionParams = _.pick( data, 'request' );
    return PromiseAdventure._firstFunction( firstFunctionParams ).then( function( newData ) {
        return _.extend( data, { firstFunctionResponse: newData } );
    } )
    .then( function( data ) {
        var secondFunctionParams = _.pick( data, 'request' );
        return PromiseAdventure._secondFunction( secondFunctionParams ).then( function( newData ) {
            return _.extend( data, { secondFunctionResponse: newData } );
        } );
    } )
    .then( function( data ) {
        var thirdFunctionParams = _.pick( data, 'property1', 'property2');
        return PromiseAdventure._thirdFunction( thirdFunctionParams ).then( function( newData ) {
            return _.extend( data, { thirdFunctionResponse: newData } );
        } );
    } );
};

PromiseAdventure._firstFunction = function( params ) {
    // can only see dereferenced "params.request" 
};
PromiseAdventure._secondFunction = function( params ) {
    // can only see dereferenced "params.request"
};
PromiseAdventure._thirdFunction = function( params ) {
    // can only see dereferenced "params.property1" and "params.property2"
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.