Skip to content

Instantly share code, notes, and snippets.

@gunar
Last active January 5, 2021 00:26
Show Gist options
  • Save gunar/fb903ad8930168364f312326cf54df5a to your computer and use it in GitHub Desktop.
Save gunar/fb903ad8930168364f312326cf54df5a to your computer and use it in GitHub Desktop.
Async Control Flow without Exceptions nor Monads
'use strict'
const toUpper = async string => {
if (string === 'invalid') return [Error('Invalid input')]
return [null, string.toUpperCase()]
}
const errorHandler = () => { console.log('There has been an error. I\'ll handle it.') }
const print = console.log
const foo = async input => {
const [err, value] = await toUpper(input)
if (err) return errorHandler(err)
print(value)
}
// Works normally.
foo('gunar')
// "GUNAR"
// Business Logic Error gets handled by errorHandler().
foo('invalid')
// "There has been an error. I'll handle it."
// Runtime Exceptions DO NOT get handled by errorHandler(),
foo(555555).catch(e => {
// but can be caught.
console.log(e)
// TypeError: string.toUpperCase is not a function
})
'use strict'
// await-to-js
const to = promise =>
promise
.then(data => [undefined, data])
.catch(err => [err, undefined])
const toUpper = async string => {
if (string === 'invalid') throw Error('Invalid input')
return string.toUpperCase()
}
const errorHandler = () => { console.log('There has been an error. I\'ll handle it.') }
const print = console.log
const foo = async input => {
const [err, value] = await to(toUpper(input))
if (err) return errorHandler(err)
print(value)
}
// Works normally.
foo('gunar')
// "GUNAR"
// Business Logic Error gets handled by errorHandler().
foo('invalid')
// "There has been an error. I'll handle it."
// Runtime Exceptions ALSO get handled by errorHandler().
foo(555555)
// "There has been an error. I'll handle it."
'use strict'
const toUpper = (string, cb) => {
if (string === 'invalid') return cb(Error('Invalid input'))
return cb(null, string.toUpperCase())
}
const errorHandler = () => {
console.log('There has been an error. I\'ll handle it.')
}
const print = (err, value) => {
if (err) return errorHandler(err)
console.log(value)
}
// Works normally.
toUpper('gunar', print)
// "GUNAR"
// Business Logic Error gets handled by errorHandler().
toUpper('invalid', print)
// "There has been an error. I'll handle it."
// Runtime Exceptions don't get handled by errorHandler(),
try {
toUpper(555555, print)
} catch (e) {
// but can be caught by try/catch.
console.log(e)
// TypeError: string.toUpperCase is not a function
}
'use strict'
const transfer = (sender, recipient, amount) => {
if (sender.account.balance < amount) throw Error('Lacking funds.')
return {
sender: { account: { balance: sender.account.balance - amount } },
recipient: { account: { balance: recipient.account.balance + amount } },
}
}
const sender = {
account: { balance: 100 },
}
const recipient = {
account: { balance: -5 },
}
try {
console.log(transfer(sender, recipient, 100))
} catch (e) {
console.log(`Error: ${e.message}`)
}
// { sender: { account: { balance: 0 } },
// recipient: { account: { balance: 95 } } }
// ^ Works.
try {
console.log(transfer(sender, recipient, 110))
} catch (e) {
console.log(`Error: ${e.message}`)
}
// Error: Lacking funds.
// ^ Proper Business Logic Error presented to the end-user.
try {
console.log(transfer(null, recipient, 110))
} catch (e) {
console.log(`Error: ${e.message}`)
}
// Error: Cannot read property 'account' of null
// ^ Runtime Exception which shouldn't have been presented to the end-user.
'use strict'
const Future = require('fluture')
const Do = require('fantasydo')
const db = {
users: {
1: { name: 'gunar' },
2: { name: 'gessner' },
},
}
const getAllUserIds = () =>
Future((rej, res) => {
// setTimeout to illustrate network call
setTimeout(res, 1000, Object.keys(db.users))
})
const getUser = id =>
Future((rej, res) => {
// setTimeout to illustrate network call
setTimeout(res, 1000, db.users[id])
})
const getFirstUserName = () =>
Do(function* () {
const users = yield getAllUserIds()
const firstUser = yield getUser(users[0])
return firstUser.name
}, Future)
getFirstUserName().fork(
error => console.log({ error }),
value => console.log({ value })
)
// { value: 'gunar' }
'use strict'
const go = require('go-for-it')
const toUpper = async string => {
if (string === 'invalid') throw Error('Invalid input')
return string.toUpperCase()
}
const errorHandler = () => { console.log('There has been an error. I\'ll handle it.') }
const print = console.log
const foo = async input => {
const [err, value] = await go(toUpper(input))
if (err) return errorHandler(err)
print(value)
}
// Works normally.
foo('gunar')
// "GUNAR"
// Business Logic Error gets handled by errorHandler().
foo('invalid')
// "There has been an error. I'll handle it."
// Runtime Exceptions DO NOT get handled by errorHandler(),
foo(555555).catch(e => {
// but can be caught.
console.log(e)
// TypeError: string.toUpperCase is not a function
})
'use strict'
// Compulsory single abstraction for handling Runtime Exceptions and Business Logic Errors.
const toUpper = async string => {
if (string === 'invalid') {
// there are multiple ways to do this instead of Promise.reject()
return Promise.reject(Error('Invalid input'))
}
// Redundant Promise.resolve() because async functions always return Promises
return Promise.resolve(string.toUpperCase())
}
const errorHandler = () => {
console.log('There has been an error. I\'ll handle it.')
}
const print = console.log
// Works normally.
toUpper('gunar').then(print).catch(errorHandler)
// "GUNAR"
// Business Logic Error gets handled by errorHandler().
toUpper('invalid').then(print).catch(errorHandler)
// "There has been an error. I'll handle it."
// Runtime Exceptions ALSO get handled by errorHandler().
toUpper(555555).then(print).catch(errorHandler)
// "There has been an error. I'll handle it."
'use strict'
const Future = require('fluture')
const thenify = f => ({
then(res, rej) { f.fork(rej, res) },
})
const future = Future((rej, res) => {
setTimeout(res, 100, 'resolved')
})
const foo = async () => {
// `await`ing Futures, WOO!
const value = await thenify(future)
return value
}
// But the output of foo() is a Promise not a Future
'use strict'
const folders = {
A: [1, 2, 3],
B: [4, 5],
C: [],
}
const values = obj =>
Object.keys(obj)
.map(key => obj[key])
const size = arr => arr.length
const sum = (a, b) => a + b
const countItems = () =>
values(folders)
.map(size)
.reduce(sum)
const clearFolder = folderName => {
if (countItems() === 0) {
throw Error('Stop cleaning up, all folders are already empty.')
}
folders[folderName] = []
}
const main = () => {
// ...
try {
Object.keys(folders).forEach(clearFolder)
} catch (e) {
console.log(e.message)
// Stop cleaning up, all folders are already empty.
}
// ...
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment