Promises vs Eventual Values

A few times on Twitter, I’ve complained that Promises are a poor conceptual stand-in for Eventual Values.

A colleague of mine pointed out that I tweet this or something like it every few months. He’s correct, I do.

The responses usually flow in saying something to the effect of “Well, if you’re not sure if a thing is a Promise or not, just use Promise.resolve(thing) and now you’re guaranteed to get either the Promise, or a Promise that resolves to the thing.”

And it’s true, that is “a” solution of sorts. It solves the “I don’t know if this is a Promise or not” problem by ensuring that you definitely do have a Promise, and not a normal value.

My problem, though, is that I’d really like to write code that interacts with values, and not Promises, and leave the machinery to the computer to work out.

Eventual Values

An Eventual Value is a value that is not yet resolved. However, by design, it is mostly indistinguishable from a “normal” value. Here’s an example:

// promise code
function add5 (x) {
  return x + 5
}

function add5Promise (x) {
  return Promise.resolve(x).then(function (x) {
    return add5(x)
  })
}

function someNumberLater () {
  return new Promise(function (resolve) {
    databaseConnector.get('numeric value').then(function (x) {
      resolve(x)
    })
  })
}

add5Promise(someNumberLater()).then(function (xplus5) {
  console.log('the number plus 5 is %d', xplus5)
})

With Eventual Values, this would look like the following:

// eventual value code
function add5 (x) {
  return x + 5
}

function someNumberLater () {
  databaseConnector.get('numeric value'))
}

console.log('the number plus 5 is %d', add5(someNumberLater()))

If you imagine getting a few numbers, any of which might be as yet unresolved, it’s even more annoying:

// promise code
function addThreeNumbers (x, y, z) {
  return Promise.all([x, y, z]).then(function (numbers) {
    return Promise.resolve(numbers[0] + numbers[1] + numbers[3])
  })
}

// eventual code
function addThreeNumbers (x, y, z) {
  return x + y + z
}

Criteria

That’s it. You interact with Eventual Values as if they’re normal synchronous values, and the machine takes care of waiting where it’s necessary and appropriate.

But Zalgo!

Indeed, this is Zalgo’s purest form. But let’s clarify exactly what’s wrong with the maybe-sync anti-pattern.

The problem with Zalgo APIs is that they’re hard to reason about. Given code like this:

someApi(function () {
  console.log('foo')
})
console.log('bar')

In a sync callback API, you know that this will always print out foo\nbar. In an async callback API, you know that this will always print out bar\nfoo. This predictability is important for human brains.

This is why the built-in dezalgo of Promises A+ is so important.

new Promise(function (resolve) {
  console.log('foo')
  resolve()
})
console.log('bar')

This code must either (a) always print foo\nbar, or (b) always print bar\nfoo. Since Promise resolution can be async, it must be async all the time, or else this predictability constraint is violated.

With Eventual Values, Zalgo is not a problem, because whether things happen synchronously or asynchronously, they always happen in the same predictable order.

function getFoo () {
  return new Eventual(function (resolve) {
    setTimeout(function () {
      resolve('foo')
    })
  })
}

function getBar () {
  return new Eventual(function (resolve) { resolve('foo') })
}

console.log(getFoo())
console.log(getBar())

In this case, it will always print foo\nbar, even though foo is resolved later, and bar is resolved immediately. Eventual Values behave like synchronous values.

Introspection, Operators, Etc.

It’d be nice to have some magic isEventual operator that could tell you if a thing was already resolved or not.

Or even a wasEventual operator to tell you whether this thing you have was ever waited upon.

But mostly, while it’s great to be able to introspect programs, it’s even better not to have to need to introspect programs. We don’t have memory address introspection in JavaScript, and no one seems to mind. Inspecting Eventual Values should be akin to peering into the dark machinery of the VM; something that’s useful once in a while, and certainly interesting from an academic point of view, but not in the normal day to day activities of the typical JavaScript programmer.

Language design is hard. It may be that there’s some really good reason for at least having a special operator or something to say “Yes, I would like any Eventual Values to be resolved before calling this function.” Maybe use functionE instead of function, I don’t know. I don’t care. I just want to stop having to stick my nose in the machinery. Ideally, we’d almost never have to care, because it only matters at the very edges of a program, when data is sent to the DOM, or written to a file or socket or terminal.

Implementation

This cannot be implemented in userland, nor should they be. Eventual Values are a language feature. Promises are an API, and easy to implement in userland.

Promises will never grow into Eventuals, because they are fundamentally different things, even though they implement a similar pattern.

Looking Async

I’ve argued in the past, quite forcefully, that synchronous code should look synchronous, and async code should look async. But again, like Zalgo, let’s not conflate “a good rule of thumb for API design” with “a language feature that would make our lives better”.

If you dig one layer deeper, you find that the only reason asynchronous code needs to look asynchronous is that we continually rely upon our human brains to deal with the timing and synchronicity of our programs.

In cases where we are relying on a meat brain to analyze a program, it is extremely important for it to look like what it is. There will likely always be some API surfaces that are asynchronous, and even in a world with Eventual Values, there is a place for APIs that are creatively asynchronous, and they should look and behave like what they are.

Anti-Promises

Promises are neat. They’re a terse and expressive way to chain together a series of potentially asynchronous actions in a way that’s relatively easy for a meat brain to make sense of.

Also, it’s quite nice how they invert control differently than callbacks. With a callback, the caller creates and passes a token to the API, with the contract that the API provider will use that token to indicate done-ness. With Promises, the API provider creates and passes a token to the caller, with the contract that the API provider will use that token to indicate done-ness.

It’s a subtle difference, but one that many people find easier to reason about. That’s fine.

However, if you have a function that takes a list of arguments, any of which may potentially be promises, and then needs to only act once all of those promises are resolved, it gets tedious quite fast. Promises tend to expose quite a lot of boilerplate to the user in these types of situations. And, since the only straightforward solution is to either turn all things into Promises, or manually check each for Promise-ness, one potentially ends up introducing a lot of nextTick() delays unnecessarily.

I don’t dislike Promises. But I do long for Eventual Values, and Promises are not those.