js-awe
Version:
Awesome js utils including - plan: An Asynchronous control flow with a functional taste - Chrono: record and visualize timelines in the console
130 lines (102 loc) • 4.17 kB
JavaScript
import assert, { deepEqual } from 'assert'
import { isPromise } from 'util/types'
import { sleepWithValue } from 'js-awe'
let a = 3
function sum(b) {
return a + b
}
testWithContext(sum, [2], 5, {})
//testWithContext(sum, [2], 5, { a: 4 }) // Fails
//testWithContext(sum, [2], 6, { a: 4 }) // Works
function testPure(fun, values, expected) {
const actual = fun(...values)
if (isPromise(actual)) actual.then((actualVal) => deepEqual(actualVal, expected, 'failed'))
else deepEqual(actual, expected, 'failed')
}
function testWithContext(fun, values, expected, context) {
Object.entries(context).forEach(([key, value]) => {
eval(`${key} = value`)
})
testPure(fun, values, expected)
}
function closureFun(a) {
return function sum(b) {
return a + b
}
}
let toTestSum = closureFun(2)
testWithContext(toTestSum, [4], 6, {})
testWithContext(toTestSum, [4], 6, { a: 9 })
function mutableStateClosureFun(a) {
return {
sum: function (b) {
return a + b
},
changeA: (newA) => (a = newA),
}
}
let toTestSumChangeScope = mutableStateClosureFun(2)
testWithContext(toTestSumChangeScope.sum, [3], 5, {})
toTestSumChangeScope.changeA(3)
//testWithContext(toTestSumChangeScope.sum, [3], 5, {}) // Fails
//testWithContext(toTestSumChangeScope.sum, [3], 6, {}) // Works
// Functions with promises as input param are pure if final result is predictable with specific values of input: Promise.resolve()
// For testing we need to fix the value of the input to be predictable, otherwise it does not meet the creteria of:
// SAME INPUT VALUES produces different results.
//
// testPromise is a pure function
async function testPromise(myProm) {
const myData = await myProm
return myData.map((el) => el + 1)
}
let myProm = Promise.resolve([2, 6, 3])
testWithContext(testPromise, [myProm], [3, 7, 4], {})
testWithContext(testPromise, [myProm], [3, 7, 4], {})
// sumWithPromises is pure
function sumWithPromises(promA, promB) {
return Promise.all([promA, promB]).then(([a, b]) => a + b)
}
const result = await sumWithPromises(Promise.resolve(3), Promise.resolve(2))
const expected = 5
assert(result === expected, `result: ${result} not equal to ${expected}`)
// I consider the following function pure as the fact that we could make the prom to resolve at different time, this will
// make the inputs different so it does not meet the requirement for "WITH SAME INPUT" returns different values
//
// The fact that this function can return different outputs for same resolve values of promises in inputs... it is not sufficient
// because the time of the resolution of the promise differs between tests. Both, values and time resolution need to meet for a
// valid purity test to be considered.
//
// If the original intention of the function was to return the first value if not undefined othewise it should return the second,
// then definetly the function is implemented incorrectly, but this is not related to the classification of purity of a function.
//
async function raceCond(promA, promB) {
let valueA, valueB
let result = await Promise.race([promA, promB])
if (result === undefined) return await promB
}
console.log(
await raceCond(
sleepWithValue(6 /* ms */, 5 /* value */),
sleepWithValue(4 /* ms */, 3 /* value */)
)
)
// The fact that the implementation above is not easy to reason is more related with a bad implementation of functional design.
// It will make more sense to isolate promises and resolve them in the non functional part of the application, and
// inject real resolve values to the core functional.
// Non functional side of the app
const [customerPrefs, customerDefaultPrefs] = await Promise.all([
getCustomerPrefs('0396d9b0'),
getCustomerDefaultPrefs(),
])
const prefs = valueOrDefault(customerPrefs, customerDefaultPrefs)
console.log(`preferences for customer: ${prefs}`)
function getCustomerPrefs(customer) {
return sleepWithValue(6 /* ms */, undefined /* value */)
}
function getCustomerDefaultPrefs(customer) {
return sleepWithValue(3 /* ms */, 5 /* value */)
}
// functional side
function valueOrDefault(valueA, valueB) {
return valueA ?? valueB
}