Broken Promises

Introduction to Alternative Async Primitives

Promise History

# PRESENTING BROKEN PROMISES

Promise History: Fantasy Land

# PRESENTING BROKEN PROMISES
  • Brian Mckenna's Proposal in terms of fp-ts would have been to add
  • Promise.of(value) --> lifting any value into the context of a Promise, a convenience function
  • Promise.chain(Promise(value)) --> flattening a Promise(Promise(value)) to just Promise(value), distinguishing the map and flatMap behaviors that currently exist in Promises now
  • (Nice to have) Make Promise.onRejected method separate
  • (Nice to have) A Promise.done method --> To make it lazy
  • From this we get applicative for free (parallelism), aka Promise.all

Promise History: Fantasy Land

# PRESENTING BROKEN PROMISES

Fantasy Land: Laziness

# PRESENTING BROKEN PROMISES
  • Promises are eager instead of lazy, making them slightly less ergonomic
const promise = new Promise((resolve, reject) => { console.log(value) })
// promise is not reusable because it's already been called!

const promiseLazy = () => new Promise((resolve, reject) => { console.log(value)})
// wrapping a promise in a function is just a promise getter

// if we allow promises to lazy we can
const promiseAPlusPlus = fetchButBetter('api.cool/123/details').then(parse).then(map)
promiseAPlusPlus.run('adam#123')

// Alternatively Brian McKenna
promiseAPlusPlus.done()

Fantasy Land:  Common Interfaces

# PRESENTING BROKEN PROMISES
  • Unlock ease of development for the developer by providing an API they are already familiar with (map, flatMap, etc.)
  • Remove overload of .then being map + flatMap, unlocking applicative (parallel)
// Assumes promises are lazy
const query = clientDB
	.map(fnMapValue)  // map over value like we would with arrays
	.flatMap(value => anotherAction(value)) // flatten a value like we would arrays

// Run the query
query.run('connection123')

const query = clientDB('connection123')
	.map(fnMapValue)  // map over value like we would with arrays
	.flatMap(value => anotherPromise(value)) // flatten a value like we would arrays

// Run the query
query.done()

Fantasy Land: Better Error Handling

# PRESENTING BROKEN PROMISES
  • Exceptions and Promise.reject behave the same
  • Exceptions are not explicit errors and shouldn't be settled, they should crash because they are unexpected behavior
  • Promise.reject is explicit and should be handled
// If a promise has an explicit error to be handled
const getDBData = (id) => {
  db.conncet()
  
  return db.query(id)
}

getDBData
  .onRejected((error) => {
    switch error {
      // a more useful switch to handle different errors
    }
  })
  .done(res => {}) // uncaught exceptions prevent the Promise from settling

Real World: Task

# PRESENTING BROKEN PROMISES
  • A Task is an async effect that can't fail such as IO (writing to a file, etc.)
// Tasks are lazy by default so they can be reused!
// They use Promises under the hood, hence the await (or use .then)
const ioTask = Task.of(1)

await ioTask() // can never fail and returns 1

// or maybe something more practical, also can be resused
// we get common methods for free like map and chain
const result = pipe(
  ioTask,
  T.map(n => n + 1), // map is array.map
  T.chain(n => T.of(n + 2)) // chain is array.flatMap
)

await result() // returns 4

Real World: TaskEither

# PRESENTING BROKEN PROMISES
  • A TaskEither is an async effect that can fail such as reading from a DB or making an API call
// Under the hood just a Task that returns an Either
const successOnly = TE.right(1)
const failOnly = TE.left(2)

const result = pipe(
  successOnly
  TE.chain(n => failOnly()),
)

await result() // E.left(2)

// Error handling is explicit if you want to get the result
await result()
  .fold(
    error => console.log(error), 
    success => console.log(success)
)

Real World: Task + TaskEither

# PRESENTING BROKEN PROMISES
  • Laziness === resuable
  • Explicit handling of errors when there are errors (Task vs TaskEither)
  • A common set of utility functions that we know and don't have to recreate
  • Explicitness of behavior instead of function overloading
  • Fantasy Land -> Real World

References

# PRESENTING BROKEN PROMISES

Code

By Adam Recvlohe

Code

A talk about the history of Promises in JS and what fp-ts does to improve them

  • 130