Promise/Future: asynchrony in 'then'

Claus Reinke claus.reinke at
Sat May 4 08:04:46 PDT 2013

>> That part I wouldn't be so sure about: in all monads, the .of equivalent
>> is effect-free (in an IO monad, it does no IO; in a non-determinism
>> monad, it is deterministic; in a failure/exception monad, it does not
>> fail; in a count-steps monad, it doesn't count).
>> If you look at those identity laws at the top again, you'll see that
>> Promise.of cannot introduce a delay for these laws to work out
>> (otherwise, the left- and right-hand sides would have different
>> numbers of ticks/turns).
> As I said, the number of ticks is unobservable if you're writing
> effect-free code.

In my use of the the term above, the "effect" of the Promise monad
would be to provide a value "maybe now, maybe later", and an
"effect-free" '.of' would be an already resolved Promise ("value
available now").

Adding ticks in operations that should allow for "effect-free" passing
of intermediate results is observable in slow-downs. That was the
topic of the blog post that got me to look into this in the first place
(performance issues with promise implementations, see thread 
opening message).

The point of insisting that promises implement a monadic interface
is that promises can reuse abstractions built for monads - that also
means that passing intermediate values around should not cause
additional delays. For instance, in the 'liftA2' example from one of 
the issue tracker threads:

there are several occurrences of '.then', via 'map' and 'ap', that 
should not delay the result by several additional turns - the only 
asynchrony in using 'liftA2' over promises should come from the 
promise parameters and possibly from the callback parameter.

However, you seem to be referring to side-effects instead (effects 
beyond returning a value in an expression, beyond the specified 
effect of a given monad).

Side-effect-free code is difficult to write in JS - I would be surprised 
if most promise implementations were not full of side-effects
(internal queues, shared pipelines, resolution). Also, so many
examples of using promises involve side-effects that this seems
to count as an established practice.

Which means that the additional code queuing will also be 
observable in code reorderings, not just delays. Which is, indeed,
the rationale for attempting to add delays in a normalized fashion,
as you state below:
> If you're not writing effect-free code, then as I said before, keeping
> the number of ticks the same regardless of the state of the promise
> when you call .then() on it is important for consistency, so it's easy
> to reason about how your code will run.

Given that many JS APIs still are heavily side-effect biased, we'll
need to take that into account. And in that world, adding delays in
parts of the promise API that should implement the common
monadic interface is very much observable, and will cause code 
written against this common interface to behave differently when
run over a promise than when run over another monad.


More information about the es-discuss mailing list