Giter Site home page Giter Site logo

lodash-fp-composition's Introduction

keep the darkside away: supercharge lodash/fp with promise- and immutable support to make code more readable, maintainable & composable

Warning: Experimental

How does this library makes my code look?

var hasPassword       = _.get('password')
var hasNoPassword     = _.negate( hasPassword )
var getUser           = opts => db.find({email:opts.email, password:opts.password})
var gotoCatch         = err => e => throw e // optionally you can log stuff here
var doAnalytics       = Promise.all([logUser, logAnalytics])
var notifyExpiryDate  = opts => return true         // mock
var userAlmostExpired = opts => return true         // mock
var updateLastLogin   = _.set('lastlogin', Date.now )
var error             = opts => err => return true // mock 
var reply             = opts => req.send(opts)

var createUser        = opts => new Promise( (resolve, reject) => {
                           opts.password = '1234'
                           db.create(opts)
                           .then( resolve )
                           .catch( resolve )
                        })


var loginUser         = _.flow() // create empty flow
                         .then( error('no email') ).when( hasNoEmail    )
                         .then( getUser           ).when( hasPassword   )
                         .then( createUser        ).when( hasNoPassword )
                         .then( doAnalytics       ).fork()
                         .then( updateLastLogin   ).fork()
                         .then( notifyExpiryDate  ).when( userAlmostExpired )
                         .then( saveUser          )
                         .then( doAnalytics       ).fork()


var loginUserAndReply = _.flow( loginUser, _.log, reply )
                         .catch( opts => error => reply({...opts,error}) )

NOTE: fork() doesn't wait for the execution of that line. Its triggers parallel execution, therefore it will never break the flow (=desired)

Summary:

  • looks easy
  • easy to maintain
  • no if/else-clutter
  • no early returns (pipeline certainty)
  • immutable
  • code is not tightly coupled to webrequest
  • no temporary variables

Because you don't want this

var doAnalytics       = Promise.all([logUser, logAnalytics])
var getUser           = db.find({email:opts.email, password:opts.password})
var notifyExpiryDate  = opts => return true         // mock
var userAlmostExpired = opts => return true         // mock
var reply             = opts => req.send(opts)

var createUser        = opts => new Promise( (resolve, reject) => {
                          opts.password = '1234'
                          db.create(opts)
                          .then( resolve )
                          .catch( resolve )
                        })

var loginUser = (opts) => new Promise( (resolve, reject) => {
  if( !opts.email ){ 
    // log stuff here
    return req.send({err:"no email"})
  }
  var user
  var getOrCreateUser
  if( opts.password ){ 
    getOrCreateUser = getUser
  }else{
    getOrCreateUser = createUser
  }
  getOrCreateUser(opts)
  .then( (u) => {
    user = u
    doAnalytics.then( () => false ).catch( () => false ) // ugly parallel code
  })
  .then( () => {
    user.lastlogin = Date.now()
    // PROBLEM: user is now modified..so code below will process an updated userobject 
  })
  .then( () => {
    if( userAlmostExpired(user) ){  // will not work because of previous problem
      notifyExpiryDate(user)        // even if it would work..
                                    // this could throw an exception
                                    // an skip code execution below
    }
  })
  .then( () => {
    doAnalytics.then( () => false ).catch( () => false ) // ugly parallel code
  })
  .then( () => saveUser(user) )
  .then( () => reply(user) )
  .catch( err => {
    if( !user ) user = opts 
    reply({ err, ...user})  
  })  

})

Issues:

  • early returns
  • needs temporary variables
  • mutability issues
  • if/else-clutter
  • unexpected halting of .then()-pipelines
  • code is tightly coupled to webrequest

Philosophy

  1. functional programming in javascript has 2 categories: the good stuff..and there's the other stuff :)
  2. _.flow (=reversed compose) is great, and reduces the amount of temporary variables
  3. Promises are great building blocks for async flow-control (and can be extended)
  4. mixing functions & promises should be hasslefree (lodash + promise = not hasslefree)
  5. accept javascript, therefore accept and expect mutable objects
Usage what this lib does comment
_.flow(... , ...) adds support for automic promise-resolving without arguments, it creates an extended promise
_.flow().then( [Function] ) always forwards processed input to next function reduces if/else statements
_.flow().then( [Function] ).fork() dont wait for the output, just forward unprocessed input to next function immutable data FTW
_.flow().then( ... ).when( isValid ) always forwards input, but processes if isValid({..}) is true prevents need of inline promise-code and early returns

NOTE: optionally you can define your own clonefunction like .fork(_.cloneDeep) e.g.

Usage

type how
nodejs with lodash var _ = require('lodash/fp')
_.mixin( require('lodash-fp-composition')
nodejs without lodash var _ = require('lodash-fp-composition')
browser without lodash <script src='https://unpkg.com/lodash-fp-composition'></script>
`<script>flow(...)</script>
browser with lodash <script src='https://unpkg.com/lodash'></script>
<script src='https://unpkg.com/lodash-fp-composition'></script>
<script>_.flow(...)</script>

Function Reference

_.flow( promise_or_function, ... )

Improved version of _.flow, which also supports automatic resolving of promises. Note: modifies output.

example: a = _.flow( new Promise(.....), _.trigger(alert), Object.keys ) a({foo:1, bar:2})

  input:{foo:1,bar:2} --->  promise --+--> Object.keys(input) ) 
                                      |
                                      +--> alert(input)
  output:['foo','bar'] 

_.trigger(fn)

trigger simply executes a function OR promise, but forwards original input as output. this comes in handy when you don't want to break a flow/chain

example: _.flow( _.trigger( alert ), _.trigger(console.dir) )({foo:"bar"})

_.when(f, g)

hipster if statement, only execute function g when function f does not return null/false/undefined

example: _.when( _.isString, console.log )("foo")

_.lensOver(path, fn)

lens over allows i/o for a nested property

example: var updateBar = _.flow( -> 123, _.log ) _.lensOver( "foo.bar", updateBar )({foo:{bar:0}}) // sets 'foo.bar' to 123 (and prints in console)

_.template_es6(es6_template)

simple es6 templates for in the browser

example: _.template_es6('${foo}', {foo:"bar"}) // outputs 'bar'

_.prefix(prefix, fn)

simple way to prefix a function which outputs a string

example: _.error = _.prefix("error: ", _.log)

_.postfix(postfix, fn)

simple way to postfix a function which outputs a string

example: _.flow( _.get('.length'), _.prefix("items", _.log) )([1, 2, 3])

_.log(str)

simple log function (which forwards input to output)

example: _.flow( doFoo, _log, doBar )({input:"foo"})

_.error(str)

simple error function (which forwards input to output)

example: _.when( !hasFoo, _.prefix("something went wrong:", _error ) )({input:"foo"})

_.mapAsync(arr, done, cb)

calls cb(data, next) for each element in arr, and continues loop based on next()-calls (last element propagates done()). Perfect to iterate over an array synchronously, while performing async operations inbetween the elements.

example: _.mapAsync([1, 2, 3], alert, (data, next) => next() )

lodash-fp-composition's People

Contributors

coderofsalvation avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

cloudxtreme

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.