declarative-optional

A Declarative way to deal with null or undefined or promises via optional and chaining

Usage no npm install needed!

<script type="module">
  import declarativeOptional from 'https://cdn.skypack.dev/declarative-optional';
</script>

README

Build Coverage License Version

Declarative-Optional

A Javascript library to write concise functional code.Combined with features of Java Optional & Promise chaining

Features

Lazily evaluated

Merging multiple Optionals

Convert to Stream

chaining async and sync functions

Most of the Java Optional Features


Installation
    npm install declarative-optional   
Usage
// Common JS
    const Optional = require( "declarative-optional");

//ES6
    import Optional from "declarative-optional";

//Increment a Number , which may be null
 Optional.of(input)
    .map(val=>val+1)
    .get()

// All the expressions will be evaluated only after you specified get()

//Increment a Number by 5, Only if its dividable by 5
Optional.of(input)
    .filter(val=>val % 5 == 0)
    .map(val=>val+5)
    .get()


   

On Asynchronous code

// Consider the async function

function getFromUserService({username, password}) {
    return new Promise((function (resolve) {
        resolve({name: "user", isAdmin: true})
    }))
}

async function login({username, password}) {

    if (null == username || null == password) {
        throw new Error("Cannot be Null")
    }

    const result = await getFromUserService(username, password)


    if (result.isAdmin) {
        redirectTo("adminPage")
    } else {
        redirectTo("userPage")
    }

}

// With Declarative Optional
async function login({username, password}) {


    const page = await Optional.of({username: user, password: pass})
        .filter(({username, password}) => (null != username && null != password))
        .map(getFromUserService)
        .map(result => result.isAdmin ? "adminPage" : "userPage")
        .toAsync();


    page.ifPresentOrElse(redirectTo, () => {
        throw new Error("Cannot be Null")
    })
}

fetch Api with Optional


    // Typical code 

    const url  ='https://jsonplaceholder.typicode.com/todos/' + item
    const rawResults = await fetch(url);
    const response = await rawResults.json();

    if (response.completed) {
        return response.title
    } else {
        return null
    }

    
    // Can be rewritten with optional as 
    return await Optional.of('https://jsonplaceholder.typicode.com/todos/' + item)
        .map(fetch)
        .map(response => response.json())
        .filter(response => response.completed == true)
        .map(response => response.title)
        .getAsync();

   
    


There are so much you can play with declarative Optional. It does have some features similar to Java Optional & RX libraries, except the code is small (one file around 4 Kb original source) and simple.

Documentation

Function Description Example
Optional.of To create a new Optional .Can be value or promise or null or undefined
 Optional.of(input)

map convert from one form to other, returns optional instance, can return async functions as well
  Optional.of(21)
  .map(val => val + 1)

filter apply a predicate function over a value
  Optional.of(21)
  .filter(val => val % 5 == 0)

flatmap if an existing function returns Optional , it will flatten the value and pass it below
// Consider a function which will return Optional
//returns Optional 
const powersOf = (name)=>Optional.of(['web-shooters','syntheitic-webbing'])

const superHeroById = (id) =>  "Spider-Man"

const res  = Optional.of(52001)
    .map(superHeroById)
    .map(powersOf )
    .flatten()
    .get()

// results ['web-shooters','syntheitic-webbing']

const res  = Optional.of(52001)
    .map(superHeroById)
    .flatmap(powersOf )
    .get()

// results ['web-shooters','syntheitic-webbing']

get evaluate all the chained functions and give the result. If no value is available , will return null
  Optional.of(21)
  .filter(val => val % 5 == 0)
  .map(val => val + 5)
  .get() ;  // returns null

  Optional.of(20)
  .filter(val => val % 5 == 0)
   .map(val => val + 5)
  .get() ;  // returns 25

Error

  Optional.of(input)      
   .map(promiseFunctionToValidateUserDetails)
   .map(promiseFunctionToValidateSomeOther)
  .get() ;  

// Error ? Use getAsync to deal with promises

orElse Evaluate all the chained functions and if result exists, give the result. If no value is available , will return value passed by
  Optional.of(21)
  .filter(val => val % 5 == 0)
  .map(val => val + 5)
  .orElse(45) ;  // returns 45 , as the evaluation will return null

  Optional.of(20)
  .filter(val => val % 5 == 0)
   .map(val => val + 5)
  .orElse(45) ;  // returns 25

stream Evaluates all the chained functions and give an array object , if the result is already an array, it will provide as it is , if its single element, it converts to an array of single element
const res  = Optional.of([23,45])
    .stream()
    .map(i=>i+1);

//returns [24,46]

const res  = Optional.of(null)
    .stream()
    .map(i=>i+1);

//returns []

const res  = Optional.of(23)
    .stream()
    .map(i=>i+1);

//returns [24]

getAsync
Evaluate all the chained functions combined with promises give another Promise<result>

 const result = await Optional.of(input)      
               .map(promiseFunctionToValidateUserDetails)
               .map(promiseFunctionToValidateRole)
                .map(regularFunctionToFormatData)
                .getAsync()

const result = await Optional.of('https://jsonplaceholder.typicode.com/todos/' + item)
                    .map(fetch)
                    .map(response => response.json())
                    .filter(response => response.completed == true)
                    .map(response => response.title)
                    .getAsync();

The below will also work fine

  const result = await Optional.of(21)
                          .filter(val => val % 5 == 0)
                          .map(val => val + 5)
                          .getAsync()  

toAsync
Evaluate all the chained functions combined with promises give another Promise<Optional> which hold the response.

All the async based results must use toAsync and then they can use the Optional consolidation functions , get ,orElse,stream etc..

  Optional.of(input)      
   .map(promiseFunctionToValidateUserDetails)
   .map(promiseFunctionToValidateSomeOther)
  .toAsync()
   .then(optionalData=>{
        // optionalData.get() holds the result
    })

 const optionalData = await Optional.of(input)      
                   .map(promiseFunctionToValidateUserDetails)
                   .map(promiseFunctionToValidateSomeOther)
                  .toAsync()

// optionalData.get() holds the result

Alternatives

There are some alternatives , Have a look into them as well

Optional.js

amidevtech/optional.js