EsPromise
Description
A way to produce <EsFuture> objects and to complete them later with a value or error.
Most of the time, the simplest way to create a future is to just use one of the creational APIs to capture the result of a single asynchronous computation:
EsFuture on: [self doSomething. result]
or, if the future represents the result of a sequence of asynchronous computations, they can be chained using EsFuture>>#then: or similar functions on <EsFuture>
doStuff
^self someAsyncOperation then: [:result | self someOtherAsyncOperation: result]
If you do need to create a Future from scratch — for example, when you're converting a callback-based API into a Future-based one — you can use an <EsPromise> as follows:
[class AsyncOperation]
instVar: promise := EsPromise new;
 
AsyncOperation>>doOperation
self startOperation.
^promise future "Send future object back to client."
 
AsyncOperation>>finishOperation: result
"Something calls this when the value is ready."
promise complete: result
 
AsyncOperation>>errorHappened: error
"If something goes wrong, call this."
promise completeError: error
Instance State
future: <EsFuture> future that is completed by this promise
Class Methods
async
  Creates a new asynchronous promise

     The promise completes the future asynchronously. That means that
     handlers registered on the future are not called immediately when
     #complete: or #completeError: is called. Instead the handlers are
     delayed until later using EsAsyncTaskScheduler>>scheduleTask:.

     Answers:
        <EsPromise>
new
  Creates a new promise

     The general workflow for creating a new future is to 1) create a
     new promise, 2) hand out its future, and, at a later point, 3) invoke
     either #complete: or #completeError:

     The promise completes the future asynchronously. That means that
     handlers registered on the future are not called immediately when
     #complete: or #completeError: is called. Instead the handlers are
     delayed until later using EsAsyncTaskScheduler>>scheduleTask:.

     Example:

     promise := EsPromise new.
     self handOut: promise future.
     ... 'At some later point' ...
     promise complete: 'completion value'

     Answers:
        <EsPromise>
sync
  Completes the future synchronously.

     This method should be avoided unless the completion of the future is
     known to be the final result of another asynchronous operation. If in doubt
     use the default promise creational api [EsPromise new].

     Using a normal, asynchronous, promise will never give the wrong
     behavior, but using a synchronous promise incorrectly can cause
     otherwise correct programs to break.

     A synchronous promise is only intended for optimizing event
     propagation when one asynchronous event immediately triggers another.
     It should not be used unless the calls to #complete: and #completeError:
     are guaranteed to occur in places where it won't break <EsFuture> invariants.

     Completing synchronously means that the promise's future will be
     completed immediately when calling the #complete: or #completeError:
     method on a synchronous promise, which also calls any handlers 
     registered on that future.

     Completing synchronously must not break the rule that when you add a
     handler on a future, that handler must not be called until the code
     that added the handler has completed.  For that reason, a synchronous
     completion must only occur at the very end (in 'tail position') of another
     synchronous event, because at that point, completing the future immediately
     is be equivalent to returning to the event loop and completing the future in the
     next scheduleTask:

     Example:
        promise := EsPromise sync.
        promise future timeout: 5 then: [-1].
        promise future then: [:v | self assert: v equals: 42].
        promise complete: 42.

     Answers:
        <EsPromise>
Instance Methods
complete
  Complete the future with `nil`.

     @Note: Calling EsPromise>>complete: or EsPromise>>completeError:
     must be done only once.

     All listeners on the future are informed about the value
complete:
  Complete the future with the supplied value @aValue.

     @aValue can be any resolved <Object> or an <EsFuture>
     If @aValue is an <EsFuture>, then this promise will wait
     for the future to complete, and then complete with the same
     success or error result.

     @Note: Calling EsPromise>>complete: or EsPromise>>completeError:
     must be done only once.

     All listeners on the future are informed about the value

     Arguments:
        aValue - <Object>
completeError:
  Complete the future with the supplied error @anError

     Completing a future with an error is an indication that an exception
     was thrown while trying to resolve the value.

     If @anError is a future, the future is used as the error value.
     If you want to complete with the result of the future, use
     [promise complete: aFuture]

     @Note: Calling EsPromise>>complete: or EsPromise>>completeError:
     must be done only once.

     Arguments:
        aValue - <Object>
completeError:stackTrace:
  Complete the future with the supplied error @anError
     and @aStackTrace

     Completing a future with an error is an indication that an exception
     was thrown while trying to resolve the value.

     If @anError is a future, the future is used as the error value.
     If you want to complete with the result of the future, use
     [promise complete: aFuture]

     @Note: Calling EsPromise>>complete: or EsPromise>>completeError:
     must be done only once.

     Arguments:
        aValue - <Object>
        aStackTrace - <EsAsyncStackTrace>
future
  Answer the future that is completed by this promise

     Answers:
        <EsFuture>
isCompleted
  Whether the future has been completed.

     Reflects whether #complete: or #completeError: has been called.
     A 'true' value doesn't necessarily mean that listeners of this future
     have been invoked yet, either because the completer usually waits until
     a later #scheduleTask: to propagate the result, or because #complete:
     was called with a future that hasn't completed yet.

     When this value is `true`, #complete: and #completeError: must not be
     called again

     Answers:
        <Boolean>
Last modified date: 04/21/2022