Security News
ESLint is Now Language-Agnostic: Linting JSON, Markdown, and Beyond
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
effects-as-data
Advanced tools
Express async workflows using pure functions.
npm i --save effects-as-data
You can run the code below using this command. You can see the code here.
npm install
npm run demo
First, create some action creators:
const httpGet = (url) => {
return {
type: 'httpGet',
url
}
}
const log = (message) => {
return {
type: 'log',
message
}
}
const writeFile = (path, data) => {
return {
type: 'writeFile',
path,
data
}
}
const userInput = (question) => {
return {
type: 'userInput',
question
}
}
Second, create handlers for the actions. This is the only place where side-effect producing code should exist.
const httpGetActionHandler = (action) => {
return get(action.url)
}
const writeFileActionHandler = (action) => {
return new Promise((resolve, reject) => {
fs.writeFile(action.path, action.data, {encoding: 'utf8'}, (err) => {
if (err) {
reject(err)
} else {
resolve({
realpath: path.resolve(action.path),
path: action.path
})
}
})
})
}
const logHandler = (action) => {
console.log(action.message)
}
const userInputHandler = (action) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
return new Promise((resolve) => {
rl.question(action.question, (answer) => {
resolve(answer)
rl.close()
})
})
}
Third, define a pure function that effects-as-data
can use to perform your business logic. This function coordinates your workflow. The function below does a lot and would normally be difficult to test:
const saveRepositories = function * (filename) {
const {payload: username} = yield userInput('\nEnter a github username: ')
const repos = yield httpGet(`https://api.github.com/users/${username}/repos`)
if (isFailure(repos)) return repos
const list = buildList(repos.payload)
yield printRepository(list, username)
const writeResult = yield writeFile(filename, JSON.stringify(repos.payload))
if (isFailure(writeResult)) return writeResult
yield log(`\nRepos Written From Github To File: ${writeResult.payload.realpath}`)
return writeResult
}
const printRepository = (list, username) => {
return [
log(`\nRepositories for ${username}`),
log(`=============================================`),
log(list)
]
}
const buildList = (repos) => {
const l1 = map(pick(['name', 'git_url']), repos)
const l2 = map(({name, git_url}) => `${name}: ${git_url}`, l1)
const l3 = l2.join('\n')
return l3
}
Fourth, test your business logic using logic-less tests. Each tuple in the array is an input-output pair.
it('should get user repos and write file', testIt(saveRepositories, () => {
const repos = [{name: 'test', git_url: 'git://...'}]
const reposListFormatted = 'test: git://...'
const writeFileResult = success({path: 'repos.json', realpath: 'r/repos.json'})
// 3 log actions return 3 success results
const printResult = [success(), success(), success()]
return [
['repos.json', userInput('\nEnter a github username: ')],
['orourkedd', httpGet('https://api.github.com/users/orourkedd/repos')],
[repos, printRepository(reposListFormatted, 'orourkedd')],
[printResult, writeFile('repos.json', JSON.stringify(repos))],
[writeFileResult, log('\nRepos Written From Github To File: r/repos.json')],
[undefined, writeFileResult]
]
}))
it('should log http error and return failure', testIt(saveRepositories, () => {
const httpError = new Error('http error!')
return [
['repos.json', userInput('\nEnter a github username: ')],
['orourkedd', httpGet('https://api.github.com/users/orourkedd/repos')],
[failure(httpError), failure(httpError)]
]
}))
it('should log file write error and return failure', testIt(saveRepositories, () => {
const repos = [{name: 'test', git_url: 'git://...'}]
const reposListFormatted = 'test: git://...'
const writeError = new Error('write error!')
// 3 log actions return 3 success results
const printResult = [success(), success(), success()]
return [
['repos.json', userInput('\nEnter a github username: ')],
['orourkedd', httpGet('https://api.github.com/users/orourkedd/repos')],
[repos, printRepository(reposListFormatted, 'orourkedd')],
[printResult, writeFile('repos.json', JSON.stringify(repos))],
[failure(writeError), failure(writeError)]
]
}))
If your tests are failing, you get a message like this:
AssertionError: expected { Object (type, path, ...) } to deeply equal { Object (type, path, ...) }
Error on Step 4
============================
Expected:
{
"type": "writeFile",
"path": "repos.json",
"data": ...
}
Actual:
{
"type": "writeFile",
"path": "wrong-file.json",
"data": ...
}
Fifth, wire it all up:
const handlers = {
httpGet: httpGetActionHandler,
writeFile: writeFileActionHandler,
log: logHandler,
userInput: userInputHandler
}
run(handlers, saveRepositories, 'repos.json').catch(console.error)
Logging all action failures explicitly can add a lot of noise to your code. Effects-as-data provides an onFailure
hook that will be called for each failed action with a detailed payload about the error:
function onFailure (payload) {
// payload:
// {
// fn: 'testFunction',
// log: [
// [42, {type: 'firstAction'}],
// [{success: true, payload: 'something from firstAction'}, {type: 'theFailingAction'}]
// ],
// errorMessage: 'Oh No!',
// errorName: 'TypeError',
// errorStack: the stack trace,
// error: the error object
// }
log(payload)
}
function * test () {
yield { type: 'firstAction' }
yield { type: 'theFailingAction' }
}
return run(handlers, test, 42, {
name: 'testFunction',
onFailure
})
FAQs
A micro abstraction layer for Javascript that makes writing, testing, and monitoring side-effects easy.
The npm package effects-as-data receives a total of 14 weekly downloads. As such, effects-as-data popularity was classified as not popular.
We found that effects-as-data demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
Security News
Members Hub is conducting large-scale campaigns to artificially boost Discord server metrics, undermining community trust and platform integrity.
Security News
NIST has failed to meet its self-imposed deadline of clearing the NVD's backlog by the end of the fiscal year. Meanwhile, CVE's awaiting analysis have increased by 33% since June.