Skip to content

Instantly share code, notes, and snippets.

@bassjacob
Created September 9, 2017 10:21
Show Gist options
  • Save bassjacob/bad4dee8873005bbc09571a2239836d3 to your computer and use it in GitHub Desktop.
Save bassjacob/bad4dee8873005bbc09571a2239836d3 to your computer and use it in GitHub Desktop.
Promise, Async Await and Cats

My issue with async/await specifically, and promises generally is that it blurs the line between a value and a value object. If you think about Promise { value } it becomes very important whether a function returns a value or a promise wrapped value. Unfortunately, JS promises don't behave like category theory objects (so much for the myriad of Promises are Monads blogposts) and will resolve nested values until done or rejected. This can make reasoning about their behaviour tricky and makes writing types for their functions tougher as well.

Let's start with my ideal in JS, just using promises:

/* pretend these functions are network calls, hence the promise.resolve */
const producesA = () => Promise.resolve(1);
const producesB = a => Promise.resolve(a + 1);
const doSomething = (a, b) => Promise.resolve(a + b);

function foo() {
  const a = producesA();
  const b = a.then(producesB);
  return Promise.all([a, b])
    .then(([a, b]) => doSomething(a, b));
}

any local error handling can be performed by adding a .catch to any of the methods, and global to the invocation of the function. It's clear at every step what is a value and what is a value object.

async function foo() {
  const a = await producesA();
  const b = await producedB(a);
  return doSomething(a, b);
}

it's not clear whether doSomething is a function that returns a promise or a value. so it's not obvious whether I should await it, or not. nor is it clear whether to catch it or not. if we insist that the function foo returns a promise, this is no longer a problem:

async function foo(): Promise<int> {
  const a = await producesA();
  const b = await producedB(a);
  return doSomething(a, b); // must return an int, otherwise it would return Promise<Promise<int>> which would break the compiler
}

this is obviously not the way promises work in practice (since they are resolved all the way down). but you can pretend they work this way with reason, which will likely have a performance impact, but you don't have to worry about safety:

/* pretend these functions are network calls */
let producesA () => Js.Promise.resolve 1; 

let producesB a => Js.Promise.resolve (a + 1);

let doSomething x y => Js.Promise.resolve (x + y);

let foo () :Js.Promise.t int => {
  let a = producesA ();
  let b = Js.Promise.then_ producesB a;
  Js.Promise.then_ (fun (v1, v2) => doSomething v1 v2) (Js.Promise.all2 (a, b))
};

if we imagine an async await syntax for reason, where let%await is the equivalent of const x = await foo(), then we could have

let producesA () => Js.Promise.resolve 1;

let producesB a => Js.Promise.resolve (a + 1);

let doSomething x y => Js.Promise.resolve (x + y);

let foo () :Js.Promise.t int => {
  let%await a = producesA ();
  let%await b = producesB a;

  doSomething a b;
}

and we know that doSomething must be a promise that returns an int.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment