Last active
January 5, 2021 00:26
-
-
Save gunar/fb903ad8930168364f312326cf54df5a to your computer and use it in GitHub Desktop.
Async Control Flow without Exceptions nor Monads
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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' } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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
See https://medium.com/@gunar/async-control-flow-without-exceptions-nor-monads-b19af2acc553