Introduction

This package implements the 'promise' abstraction for asynchronous programming. This document is the reference for commands and classes implemented by the package. For a tutorial introduction to promises, see the blog posts at http://www.magicsplat.com/blog/tags/promises/.

Download and install

The package is distributed as a single Tcl module and can be downloaded from http://sourceforge.net/projects/tcl-promise/files/. It should be placed in any of the directories returned by the 'tcl::tm::path list' command in your Tcl installation.

Loading and requirements

The package requires Tcl 8.6 or later and is loaded with the standard command

    package require promise

All functionality related to promises requires the Tcl event loop to be running.

::promise

Promises

The promise abstraction encapsulates the eventual result of a possibly asynchronous operation.

This package follows the terminology used in the Javascript world, in particular the ECMAScript 2015 Language specification though details of implementation differ.

From an application's perspective, a [Promise] object may be in one of three states:

  • 'FULFILLED'
  • 'REJECTED'
  • 'PENDING', if it is neither of the above

Though the above specification does not explicitly assign meanings to these states, in practice 'FULFILLED' and 'REJECTED' are associated with successful and failed completion of the operation respectively while 'PENDING' reflects the operation has not completed.

Some additional terms:

A promise is said to be settled if it is either in the 'FULFILLED' or 'REJECTED' state. A promise that is settled will thereafter never change state.

A promise is said to be resolved if it is settled or if it is attached to the state of another promise.

Applications can register callbacks to be run when a promise is settled. These callbacks are refered to as reactions.

Promises are implemented by the [::promise::Promise] class.

Constructing promises

Promises are constructed by creating instances of the [::promise::Promise] class. The constructor is passed a script that should initiate an asynchronous operation and at some point (immediately or in the future) settle the promise by calling its [fulfill] or [reject] method. Here is a simple example of creating a timer based promise (the implementation of the [ptimer] command).

    return [promise::Promise new [lambda {millisecs value prom} {
        after $millisecs [list $prom fulfill $value]
    } 1000 "Timed out"]]

The package includes several commands for constructing promises for common operations. These are layered on top of [Promise] and are present for convenience. Note these are all asynchronous in nature.

[pconnect] - establishes a socket client connection [pexec] - runs an external program collecting its output [pgeturl] - retrieves a URL using the [http] package [ptask] - runs a script in a separate Tcl thread [ptimeout] - rejects a promise when a timer expires [ptimer] - fulfills sa promise when a timer expires [pworker] - runs a script in a Tcl thread pool

Settling promises

When the asynchronous code associated with a promise completes, either successfully or with an error, it has to update the promise with the result value. On a successful completion, the [Promise] object's [Promise.fulfill] method should be called. Likewise, on an error or unsuccessful completion, the [Promise.reject] method should be invoked. In both cases, the value with which the promise should be fulfilled (or rejected) should be passed to the method.

In the preceding example, the 'after' callback settles the promise by calling its 'fulfill' method.

    $prom fulfill $value

Promise reactions

An application will generally register callbacks, called reactions as in the ES6 specifications, to be invoked (asynchronously) when the promise is settled.

In the simplest case, the reactions are registered with the [Promise.done] method. In our example above, calling

    $prom done puts

would print

    Timed out

when the promise was fulfilled by the 'after' callback.

The 'done' method may be called multiple times and each reaction registered through it will be run when the promise is settled.

Chaining promises

In more complex scenarios, the application may wish to take additional asynchronous actions when one is completed. In this case, it can make use of the [Promise.then] method instead of, or in addition to, the 'done' method. For example, if we wanted to run another timer after the first one completes, the following code would do the job. Here we use the convenience [timer] command to illustrate.

    set prom1 [promise::ptimer 1000 "Timer 1 expired"]
    set prom2 [$prom1 then [lambda {value} {
        puts $value
        promise::then_chain [promise::ptimer 2000 "Timer 2 expired"]
    }]]
    $prom2 done puts

After the first timer is settled, the reaction registered by the [then] method is run. This chains another promise based on a second timer. You should see

    Timer 1 expired
    Timer 2 expired

about 2 seconds apart.

Combining promises

One of the biggest benefits of promises stems from the ability to easily combine them.

You can initiate multiple asynchronous operations and then use the [all] or [all*] commands to register an reaction for when all of them complete.

    set calculation [all* [ptask {expr 2+3}] [ptask {expr 4+5}]]
    $calculation done [lambda {values} {
        puts [tcl::mathop::+ {*}$values]
    }] [lambda {errval} {puts "Error: [lindex $errval 0]"}]

Conversely, you can use the [race] or [race*] commands to schedule an reaction for when any one of several operations completes.

Cleaning up

Cleaning up a series of promise-based async operations has two aspects. The first is to clean up any resources that were allocated. The second is to destroy the [Promise] object itself.

For the first, an application can call the [Promise.cleanup] method to register a reaction to run when the promise is settled. Note this is run when the promise is settled, not when the object is destroyed.

Regarding destroying the [Promise] objects themselves, normally TclOO objects are not garbage collected and have to be explicitly destroyed. In the case of promises, because of their asynchronous nature, it is often not clear to applications when the promise objects should be destroyed.

Therefore the package internally manages the lifetime of Promise objects such that they are automatically destroyed once they are settled and at least one fulfillment or rejection reaction has been run. This removes the burden from the application in the most common usage scenarios. In cases where the application wants the object to persist, for example, when the resolved value is accessed multiple times, it can use the 'ref' and 'unref' methods of a [::promise::Promise] object to explicitly manage its lifetime.

Commands

all [::promise]

promise, Top

Returns a promise that fulfills or rejects when all promises in the $promises argument have fulfilled or any one has rejected.

all promises

Parameters
promises
Return value

Returns a promise that fulfills or rejects when all promises in the $promises argument have fulfilled or any one has rejected.

Description
promises a list of Promise objects

If any of $promises rejects, then the promise returned by the command will reject with the same value. Otherwise, the promise will fulfill when all promises have fulfilled. The resolved value will be a list of the resolved values of the contained promises.

proc ::promise::all {promises} {

    # Returns a promise that fulfills or rejects when all promises
    # in the $promises argument have fulfilled or any one has rejected.
    #   promises - a list of Promise objects
    # If any of $promises rejects, then the promise returned by the
    # command will reject with the same value. Otherwise, the promise
    # will fulfill when all promises have fulfilled.
    # The resolved value will be a list of the resolved
    # values of the contained promises.

    set all_promise [Promise new [lambda {promises prom} {
        set npromises [llength $promises]
        if {$npromises == 0} {
            $prom fulfill {}
            return
        }

        # Ask each promise to update us when resolved.
        foreach promise $promises {
            $promise done  [list ::promise::_all_helper $prom $promise FULFILLED]  [list ::promise::_all_helper $prom $promise REJECTED]
        }

        # We keep track of state with a dictionary that will be
        # stored in $prom with the following keys:
        #  PROMISES - the list of promises in the order passed
        #  PENDING_COUNT - count of unresolved promises
        #  RESULTS - dictionary keyed by promise and containing resolved value
        set all_state [list PROMISES $promises PENDING_COUNT $npromises RESULTS {}]

        $prom setdata ALLPROMISES $all_state
    } $promises]]

    return $all_promise
}

all* [::promise]

promise, Top

Returns a promise that fulfills or rejects when all promises in the $args argument have fulfilled or any one has rejected.

all* args

Parameters
argsAdditional options.
Return value

Returns a promise that fulfills or rejects when all promises in the $args argument have fulfilled or any one has rejected.

Description
args list of Promise objects

This command is identical to the all command except that it takes multiple arguments, each of which is a Promise object. See [all] for a description.

proc ::promise::all* {args} {

    # Returns a promise that fulfills or rejects when all promises
    # in the $args argument have fulfilled or any one has rejected.
    # args - list of Promise objects
    # This command is identical to the all command except that it takes
    # multiple arguments, each of which is a Promise object. See [all]
    # for a description.
    return [all $args]
}

async [::promise]

promise, Top

Defines an procedure that will run a script asynchronously as a coroutine.

async name paramdefs body

Parameters
name name of the procedure
paramdefs the parameter definitions to the procedure in the same form as passed to the standard 'proc' command
body the script to be executed
Return value

Returns a promise that will be settled with the result of the script.

Description

Defines an procedure that will run a script asynchronously as a coroutine.

When the defined procedure $name is called, it runs the supplied $body within a new coroutine. The return value from the $name procedure call will be a promise that will be fulfilled when the coroutine completes normally or rejected if it completes with an error.

Note that the passed $body argument is not the body of the the procedure $name. Rather it is run as an anonymous procedure in the coroutine but in the same namespace context as $name. Thus the caller or the $body script must not make any assumptions about relative stack levels, use of 'uplevel' etc.

The primary purpose of this command is to make it easy, in conjunction with the [await] command, to wrap a sequence of asynchronous operations as a single computational unit.

proc ::promise::async {name paramdefs body} {

    # Defines an procedure that will run a script asynchronously as a coroutine.
    # name - name of the procedure
    # paramdefs - the parameter definitions to the procedure in the same
    #   form as passed to the standard 'proc' command
    # body - the script to be executed
    #
    # When the defined procedure $name is called, it runs the supplied $body
    # within a new coroutine. The return value from the $name procedure call
    # will be a promise that will be fulfilled when the coroutine completes
    # normally or rejected if it completes with an error.
    #
    # Note that the passed $body argument is not the body of the
    # the procedure $name. Rather it is run as an anonymous procedure in
    # the coroutine but in the same namespace context as $name. Thus the
    # caller or the $body script must not make any assumptions about
    # relative stack levels, use of 'uplevel' etc.
    #
    # The primary purpose of this command is to make it easy, in
    # conjunction with the [await] command, to wrap a sequence of asynchronous
    # operations as a single computational unit.
    #
    # Returns a promise that will be settled with the result of the script.
    if {![string equal -length 2 "$name" "::"]} {
        set ns [uplevel 1 namespace current]
        set name ${ns}::$name
    } else {
        set ns ::
    }
    set tmpl {
        proc %NAME% {%PARAMDEFS%} {
            set p [promise::Promise new [promise::lambda {real_args prom} {
                coroutine ::promise::async#[info cmdcount] {*}[promise::lambda {p args} {
                    upvar #1 _current_async_promise current_p
                    set current_p $p
                    set status [catch [list apply [list {%PARAMDEFS%} {%BODY%} %NS%] {*}$args] res ropts]
                    if {$status == 0} {
                        $p fulfill $res
                    } else {
                        $p reject $res $ropts
                    }
                } $prom {*}$real_args]
            } [lrange [info level 0] 1 end]]]
            return $p
        }
    }
    eval [string map [list %NAME% $name  %PARAMDEFS% $paramdefs  %BODY% $body  %NS% $ns] $tmpl]
}

async_chain [::promise]

promise, Top

Chains a promise for an async procedure to the specified promise.

async_chain prom

Parameters
prom the promise to which the async promise is to be linked.
Return value

Returns an empty string.

Description

Chains a promise for an async procedure to the specified promise.

This command must only be called with the context of an [async] procedure.

proc ::promise::async_chain {prom} {

    # Chains a promise for an async procedure to the specified promise.
    #  prom - the promise to which the async promise is to be linked.
    # This command must only be called with the context of an [async]
    # procedure.
    #
    # Returns an empty string.
    upvar #1 _current_async_promise current_p
    if {![info exists current_p]} {
        error "async_chain called from outside an async context."
    }
    $current_p chain $prom
    return
}

async_fulfill [::promise]

promise, Top

Fulfills a promise for an async procedure with the specified value.

async_fulfill val

Parameters
val the value with which to fulfill the promise
Return value

Returns an empty string.

Description

Fulfills a promise for an async procedure with the specified value.

This command must only be called with the context of an [async] procedure.

proc ::promise::async_fulfill {val} {

    # Fulfills a promise for an async procedure with the specified value.
    #  val - the value with which to fulfill the promise
    # This command must only be called with the context of an [async]
    # procedure.
    #
    # Returns an empty string.
    upvar #1 _current_async_promise current_p
    if {![info exists current_p]} {
        error "async_fulfill called from outside an async context."
    }
    $current_p fulfill $val
    return
}

async_reject [::promise]

promise, Top

Rejects a promise for an async procedure with the specified value.

async_reject val edict

Parameters
val the value with which to reject the promise
edict(optional, default ) error dictionary for rejection
Return value

Returns an empty string.

Description

Rejects a promise for an async procedure with the specified value.

This command must only be called with the context of an [async] procedure.

proc ::promise::async_reject {val {edict {}}} {

    # Rejects a promise for an async procedure with the specified value.
    #  val - the value with which to reject the promise
    #  edict - error dictionary for rejection
    # This command must only be called with the context of an [async]
    # procedure.
    #
    # Returns an empty string.
    upvar #1 _current_async_promise current_p
    if {![info exists current_p]} {
        error "async_reject called from outside an async context."
    }
    $current_p reject $val $edict
    return
}

await [::promise]

promise, Top

Waits for a promise to be settled and returns its resolved value.

await prom

Parameters
prom the promise that is to be waited on
Return value

Returns the resolved value of $prom if it is fulfilled or raises an error if it is rejected.

Description

Waits for a promise to be settled and returns its resolved value.

This command may only be used from within a procedure constructed with the [async] command or any code invoked from it.

proc ::promise::await {prom} {

    # Waits for a promise to be settled and returns its resolved value.
    #   prom - the promise that is to be waited on
    # This command may only be used from within a procedure constructed
    # with the [async] command or any code invoked from it.
    #
    # Returns the resolved value of $prom if it is fulfilled or raises an error
    # if it is rejected.
    set coro [info coroutine]
    if {$coro eq ""} {
        throw {PROMISE AWAIT NOTCORO} "await called from outside a coroutine"
    }
    $prom done [list $coro success] [list $coro fail]
    lassign [yieldto return -level 0] status val ropts
    if {$status eq "success"} {
        return $val
    } else {
        return -options $ropts $val
    }
}

document [::promise]

promise, Top

Generates documentation for the package in HTML format.

document path args

Parameters
path path to the output file
argsAdditional options.
Description

Generates documentation for the package in HTML format.

proc ::promise::document {path args} {

    # Generates documentation for the package in HTML format.
    # path - path to the output file

    package require ruff

    lappend introduction Introduction {
        This package implements the 'promise' abstraction for
        asynchronous programming. This document is the reference for
        commands and classes implemented by the package. For a
        tutorial introduction to promises, see the blog posts at
        http://www.magicsplat.com/blog/tags/promises/.
    } {Download and install} {
        The package is distributed as a single Tcl module and
        can be downloaded from
        http://sourceforge.net/projects/tcl-promise/files/. It should
        be placed in any of the directories returned by
        the 'tcl::tm::path list' command in your Tcl installation.
    } {Loading and requirements} {
        The package requires Tcl 8.6 or later and is loaded with the
        standard command

            package require promise

        All functionality related to promises requires the Tcl event loop
        to be running.
    }

    lappend docs {Promises} {
        The promise abstraction encapsulates the eventual result of a
        possibly asynchronous operation.

        This package follows the terminology used in the Javascript world,
        in particular the ECMAScript 2015 Language specification though
        details of implementation differ.

        From an application's perspective, a [Promise] object may be in one
        of three states:

        - 'FULFILLED'
        - 'REJECTED'
        - 'PENDING', if it is neither of the above

        Though the above specification does not explicitly assign meanings
        to these states, in practice 'FULFILLED' and 'REJECTED' are associated
        with successful and failed completion of the operation respectively
        while 'PENDING' reflects the operation has not completed.

        Some additional terms:

        A promise is said to be settled if it is either in the 'FULFILLED'
        or 'REJECTED' state. A promise that is settled will thereafter never
        change state.

        A promise is said to be resolved if it is settled or if it
        is attached to the state of another promise.

        Applications can register callbacks to be run when a promise
        is settled. These callbacks are refered to as reactions.

        Promises are implemented by the [::promise::Promise] class.
    } {Constructing promises} {
        Promises are constructed by creating instances of the
        [::promise::Promise] class. The constructor is passed a script
        that should initiate an asynchronous operation and at some point
        (immediately or in the future) settle the promise by calling
        its [fulfill] or [reject] method. Here is a simple example
        of creating a timer based promise
        (the implementation of the [ptimer] command).

            return [promise::Promise new [lambda {millisecs value prom} {
                after $millisecs [list $prom fulfill $value]
            } 1000 "Timed out"]]

        The package includes several commands for constructing promises
        for common operations. These are layered on top
        of [Promise] and are present for convenience.
        Note these are all asynchronous in nature.

        [pconnect] - establishes a socket client connection
        [pexec] - runs an external program collecting its output
        [pgeturl] - retrieves a URL using the [http] package
        [ptask] - runs a script in a separate Tcl thread
        [ptimeout] - rejects a promise when a timer expires
        [ptimer] - fulfills sa promise when a timer expires
        [pworker] - runs a script in a Tcl thread pool

    } {Settling promises} {
        When the asynchronous code associated with a promise completes,
        either successfully or with an error, it has to update the
        promise with the result value. On a successful completion,
        the [Promise] object's [Promise.fulfill] method should be called.
        Likewise, on an error or unsuccessful completion, the [Promise.reject]
        method should be invoked. In both cases, the value with which
        the promise should be fulfilled (or rejected) should be passed
        to the method.

        In the preceding example, the 'after' callback settles the
        promise by calling its 'fulfill' method.

            $prom fulfill $value
    } {Promise reactions} {
        An application will generally register callbacks,
        called reactions as in the ES6 specifications, to be invoked
        (asynchronously) when the promise is settled.

        In the simplest case, the reactions are registered with the
        [Promise.done] method. In our example above, calling

            $prom done puts

        would print

            Timed out

        when the promise was fulfilled by the 'after' callback.

        The 'done' method may be called multiple times and each
        reaction registered through it will be run when the promise
        is settled.
    } {Chaining promises} {
        In more complex scenarios, the application may wish to take
        additional asynchronous actions when one is completed. In this
        case, it can make use of the [Promise.then] method instead of,
        or in addition to, the 'done' method. For example, if
        we wanted to run another timer after the first one completes,
        the following code would do the job. Here we use the
        convenience [timer] command to illustrate.

            set prom1 [promise::ptimer 1000 "Timer 1 expired"]
            set prom2 [$prom1 then [lambda {value} {
                puts $value
                promise::then_chain [promise::ptimer 2000 "Timer 2 expired"]
            }]]
            $prom2 done puts

        After the first timer is settled, the reaction registered by
        the [then] method is run. This chains another promise based
        on a second timer. You should see

            Timer 1 expired
            Timer 2 expired

        about 2 seconds apart.
    } {Combining promises} {
        One of the biggest benefits of promises stems from the ability
        to easily combine them.

        You can initiate multiple asynchronous operations and then
        use the [all] or [all*] commands to
        register an reaction for when all of them complete.

            set calculation [all* [ptask {expr 2+3}] [ptask {expr 4+5}]]
            $calculation done [lambda {values} {
                puts [tcl::mathop::+ {*}$values]
            }] [lambda {errval} {puts "Error: [lindex $errval 0]"}]

        Conversely, you can use the [race] or [race*] commands to schedule
        an reaction for when any one of several operations completes.

    } {Cleaning up} {
        Cleaning up a series of promise-based async operations has two
        aspects. The first is to clean up any resources that were
        allocated. The second is to destroy the [Promise] object itself.

        For the first, an application can call the [Promise.cleanup]
        method to register a reaction to run when the promise is
        settled. Note this is run when the promise is settled, not
        when the object is destroyed.

        Regarding destroying the [Promise] objects themselves, normally
        TclOO objects are not garbage collected and have to be explicitly
        destroyed. In the case of promises, because of their asynchronous
        nature, it is often not clear to applications when the promise
        objects should be destroyed.

        Therefore the package internally manages the lifetime of Promise
        objects such that they are automatically destroyed once they are
        settled and at least one fulfillment or rejection reaction has been
        run. This removes the burden from the application in the most common
        usage scenarios. In cases where the application wants the object to
        persist, for example, when the resolved value is accessed multiple
        times, it can use the 'ref' and 'unref' methods of a
        [::promise::Promise] object to explicitly manage its lifetime.

    }

    foreach {title docstring} $introduction {
        lappend introlist $title [ruff::extract_docstring $docstring]
    }

    foreach {title docstring} $docs {
        lappend doclist $title [ruff::extract_docstring $docstring]
    }
    ::ruff::document_namespaces html [namespace current]  -includesource true  -hidesourcecomments true  -autolink false  -recurse true  -output $path  -titledesc "promise (V[version])"  -copyright "[clock format [clock seconds] -format %Y] Ashok P. Nadkarni"  {*}$args  -preamble [dict create :: $introlist ::promise $doclist]
}

eventloop [::promise]

promise, Top

Waits in the eventloop until the specified promise is settled.

eventloop prom

Parameters
prom the promise to be waited on
Return value

Returns the resolved value of $prom if it is fulfilled or raises an error if it is rejected.

Description

Waits in the eventloop until the specified promise is settled.

The command enters the event loop in similar fashion to the Tcl [vwait] command except that instead of waiting on a variable the command waits for the specified promise to be settled. As such it has the same caveats as the vwait command in terms of care being taken in nested calls etc.

The primary use of the command is at the top level of a script to wait for one or more promise based tasks to be completed. Again, similar to the vwait forever idiom.

proc ::promise::eventloop {prom} {

    # Waits in the eventloop until the specified promise is settled.
    #  prom - the promise to be waited on
    # The command enters the event loop in similar fashion to the
    # Tcl [vwait] command except that instead of waiting on a variable
    # the command waits for the specified promise to be settled. As such
    # it has the same caveats as the vwait command in terms of care
    # being taken in nested calls etc.
    #
    # The primary use of the command is at the top level of a script
    # to wait for one or more promise based tasks to be completed. Again,
    # similar to the vwait forever idiom.
    #
    #
    # Returns the resolved value of $prom if it is fulfilled or raises an error
    # if it is rejected.

    set varname [namespace current]::_pwait_[info cmdcount]
    $prom done  [lambda {varname result} {
            set $varname [list success $result]
        } $varname]  [lambda {varname error ropts} {
            set $varname [list fail $error $ropts]
        } $varname]
    vwait $varname
    lassign [set $varname] status result ropts
    if {$status eq "success"} {
        return $result
    } else {
        return -options $ropts $result
    }
}

lambda [::promise]

promise, Top

Creates an anonymous procedure and returns a command prefix for it.

lambda params body args

Parameters
params parameter definitions for the procedure
body body of the procedures
args additional arguments to be passed to the procedure when it is invoked
Description

Creates an anonymous procedure and returns a command prefix for it.

This is just a convenience command since anonymous procedures are commonly useful with promises. The lambda package from tcllib is identical in function.

proc ::promise::lambda {params body args} {

    # Creates an anonymous procedure and returns a command prefix for it.
    #   params - parameter definitions for the procedure
    #   body - body of the procedures
    #   args - additional arguments to be passed to the procedure when it
    #     is invoked
    #
    # This is just a convenience command since anonymous procedures are
    # commonly useful with promises. The lambda package from tcllib
    # is identical in function.

    return [list ::apply [list $params $body] {*}$args]
}

pconnect [::promise]

promise, Top

Returns a promise that will be fulfilled when the a socket connection is completed.

pconnect args

Parameters
argsAdditional options.
Return value

Returns a promise that will be fulfilled when the a socket connection is completed.

Description
args arguments to be passed to the Tcl 'socket' command

This is a wrapper for the async version of the Tcl 'socket' command. If the connection completes, the promise is fulfilled with the socket handle. In case of errors (e.g. if the address cannot be fulfilled), the promise is rejected with the 'reason' parameter containing the error message and the 'edict' parameter containing the Tcl error dictionary.

proc ::promise::pconnect {args} {

    # Returns a promise that will be fulfilled when the a socket connection
    # is completed.
    #  args - arguments to be passed to the Tcl 'socket' command
    # This is a wrapper for the async version of the Tcl 'socket' command.
    # If the connection completes, the promise is fulfilled with the
    # socket handle.
    # In case of errors (e.g. if the address cannot be fulfilled), the
    # promise is rejected with the 'reason' parameter containing the
    # error message and the 'edict' parameter containing the Tcl error
    # dictionary.
    #
    return [Promise new [lambda {so_args prom} {
        set so [socket -async {*}$so_args]
        fileevent $so writable [promise::lambda {prom so} {
            fileevent $so writable {}
            set err [chan configure $so -error]
            if {$err eq ""} {
                $prom fulfill $so
            } else {
                catch {throw {PROMISE PCONNECT FAIL} $err} err edict
                $prom reject $err $edict
            }
        } $prom $so]
    } $args]]
}

pexec [::promise]

promise, Top

Runs an external program and returns a promise for its output.

pexec args

Parameters
args program and its arguments as passed to the Tcl 'open' call for creating pipes
Return value

Returns a promise that will be settled by the result of the program

Description

Runs an external program and returns a promise for its output.

If the program runs without errors, the promise is fulfilled by its standard output content. Otherwise promise is rejected.

proc ::promise::pexec {args} {

    # Runs an external program and returns a promise for its output.
    #  args - program and its arguments as passed to the Tcl 'open' call
    #    for creating pipes
    # If the program runs without errors, the promise is fulfilled by its
    # standard output content. Otherwise
    # promise is rejected.
    #
    # Returns a promise that will be settled by the result of the program
    return [Promise new [lambda {open_args prom} {
        set chan [open |$open_args r]
        fconfigure $chan -blocking 0
        fileevent $chan readable [list promise::_read_channel $prom $chan ""]
    } $args]]
}

pfulfilled [::promise]

promise, Top

Returns a new promise that is already fulfilled with the specified value.

pfulfilled value

Parameters
value
Return value

Returns a new promise that is already fulfilled with the specified value.

Description
value the value with which to fulfill the created promise
proc ::promise::pfulfilled {value} {

    # Returns a new promise that is already fulfilled with the specified value.
    #  value - the value with which to fulfill the created promise
    return [Promise new [lambda {value prom} {
        $prom fulfill $value
    } $value]]
}

pgeturl [::promise]

promise, Top

Returns a promise that will be fulfilled when the a URL is fetched.

pgeturl url args

Parameters
url
argsAdditional options.
Return value

Returns a promise that will be fulfilled when the a URL is fetched.

Description
url the URL to fetch
args arguments to pass to the [http::geturl] command

This command invokes the asynchronous form of the [http::geturl] command of the 'http' package. If the operation completes with a status of 'ok', the returned promise is fulfilled with the contents of the http state array (see the documentation of [http::geturl]). If the the status is anything else, the promise is rejected with the 'reason' parameter to the reaction containing the error message and the 'edict' parameter containing the Tcl error dictionary with an additional key 'http_state', containing the contents of the http state array.

proc ::promise::pgeturl {url args} {

    # Returns a promise that will be fulfilled when the a URL is fetched.
    #   url - the URL to fetch
    #   args - arguments to pass to the [http::geturl] command
    # This command invokes the asynchronous form of the [http::geturl] command
    # of the 'http' package. If the operation completes with a status of
    # 'ok', the returned promise is fulfilled with the contents of the
    # http state array (see the documentation of [http::geturl]). If the
    # the status is anything else, the promise is rejected with
    # the 'reason' parameter to the reaction containing the error message
    # and the 'edict' parameter containing the Tcl error dictionary
    # with an additional key 'http_state', containing the
    # contents of the http state array.

    uplevel #0 {package require http}
    proc pgeturl {url args} {
        set prom [Promise new [lambda {http_args prom} {
            http::geturl {*}$http_args -command [promise::lambda {prom tok} {
                upvar #0 $tok http_state
                if {$http_state(status) eq "ok"} {
                    $prom fulfill [array get http_state]
                } else {
                    if {[info exists http_state(error)]} {
                        set msg [lindex $http_state(error) 0]
                    }
                    if {![info exists msg] || $msg eq ""} {
                        set msg "Error retrieving URL."
                    }
                    catch {throw {PROMISE PGETURL} $msg} msg edict
                    dict set edict http_state [array get http_state]
                    $prom reject $msg $edict
                }
                http::cleanup $tok
            } $prom]
        } [linsert $args 0 $url]]]
        return $prom
    }
    tailcall pgeturl $url {*}$args
}

prejected [::promise]

promise, Top

Returns a new promise that is already rejected.

prejected value edict

Parameters
value
edict(optional, default )
Return value

Returns a new promise that is already rejected.

Description
value the value with which to reject the promise
edict error dictionary for rejection

By convention, $value should be of the format returned by [rejection].

proc ::promise::prejected {value {edict {}}} {

    # Returns a new promise that is already rejected.
    #  value - the value with which to reject the promise
    #  edict - error dictionary for rejection
    # By convention, $value should be of the format returned by
    # [rejection].
    return [Promise new [lambda {value edict prom} {
        $prom reject $value $edict
    } $value $edict]]
}

ptask [::promise]

promise, Top

Creates a new Tcl thread to run the specified script and returns a promise for the script results.

ptask script

Parameters
script script to run in the thread
Return value

Returns a promise that will be settled by the result of the script

Description

Creates a new Tcl thread to run the specified script and returns a promise for the script results.

The `ptask` command runs the specified script in a new Tcl thread. The promise returned from this command will be fulfilled with the result of the script if it completes successfully. Otherwise, the promise will be rejected with an with the 'reason' parameter containing the error message and the 'edict' parameter containing the Tcl error dictionary from the script failure.

Note that $script is a standalone script in that it is executed in a new thread with a virgin Tcl interpreter. Any packages used by $script have to be explicitly loaded, variables defined in the the current interpreter will not be available in $script and so on.

The command requires the Thread package to be loaded.

proc ::promise::ptask {script} {

    # Creates a new Tcl thread to run the specified script and returns
    # a promise for the script results.
    #   script - script to run in the thread
    # Returns a promise that will be settled by the result of the script
    #
    # The `ptask` command runs the specified script in a new Tcl
    # thread. The promise returned from this command will be fulfilled
    # with the result of the script if it completes
    # successfully. Otherwise, the promise will be rejected with an
    # with the 'reason' parameter containing the error message
    # and the 'edict' parameter containing the Tcl error dictionary
    # from the script failure.
    #
    # Note that $script is a standalone script in that it is executed
    # in a new thread with a virgin Tcl interpreter. Any packages used
    # by $script have to be explicitly loaded, variables defined in the
    # the current interpreter will not be available in $script and so on.
    #
    # The command requires the Thread package to be loaded.

    uplevel #0 package require Thread
    proc [namespace current]::ptask script {
        return [Promise new [lambda {script prom} {
            set thread_script [string map [list %PROM% $prom %TID% [thread::id] %SCRIPT% $script] {
                set retcode [catch {%SCRIPT%} result edict]
                if {$retcode == 0 || $retcode == 2} {
                    # ok or return
                    set response [list ::promise::safe_fulfill %PROM% $result]
                } else {
                    set response [list ::promise::safe_reject %PROM% $result $edict]
                }
                thread::send -async %TID% $response
            }]
            thread::create $thread_script
        } $script]]
    }
    tailcall [namespace current]::ptask $script
}

ptimeout [::promise]

promise, Top

Returns a promise that will be rejected when the specified time has elapsed.

ptimeout millisecs value

Parameters
millisecs
value(optional, default Operation timed out.)
Return value

Returns a promise that will be rejected when the specified time has elapsed.

Description
millisecs time interval in milliseconds
value the value with which the promise is to be rejected

In case of errors (e.g. if $milliseconds is not an integer), the promise is rejected with the 'reason' parameter set to $value and the 'edict' parameter set to a Tcl error dictionary.

Also see [ptimer] which is similar but fulfills the promise instead of rejecting it.

proc ::promise::ptimeout {millisecs {value {Operation timed out.}}} {

    # Returns a promise that will be rejected when the specified time has
    # elapsed.
    #  millisecs - time interval in milliseconds
    #  value - the value with which the promise is to be rejected
    # In case of errors (e.g. if $milliseconds is not an integer), the
    # promise is rejected with the 'reason' parameter set to $value
    # and the 'edict' parameter set to a Tcl error dictionary.
    #
    # Also see [ptimer] which is similar but fulfills the promise instead
    # of rejecting it.

    return [Promise new [lambda {millisecs value prom} {
        if {![string is integer -strict $millisecs]} {
            # We don't want to accept "idle", "cancel" etc. for after
            throw {PROMISE TIMER INVALID} "Invalid timeout value \"$millisecs\"."
        }
        after $millisecs [::promise::lambda {prom msg} {
            catch {throw {PROMISE TIMER EXPIRED} $msg} msg edict
            ::promise::safe_reject $prom $msg $edict
        } $prom $value]
    } $millisecs $value]]
}

ptimer [::promise]

promise, Top

Returns a promise that will be fulfilled when the specified time has elapsed.

ptimer millisecs value

Parameters
millisecs
value(optional, default Timer expired.)
Return value

Returns a promise that will be fulfilled when the specified time has elapsed.

Description
millisecs time interval in milliseconds
value the value with which the promise is to be fulfilled

In case of errors (e.g. if $milliseconds is not an integer), the promise is rejected with the 'reason' parameter set to an error message and the 'edict' parameter set to a Tcl error dictionary.

Also see [ptimeout] which is similar but rejects the promise instead of fulfilling it.

proc ::promise::ptimer {millisecs {value {Timer expired.}}} {

    # Returns a promise that will be fulfilled when the specified time has
    # elapsed.
    #  millisecs - time interval in milliseconds
    #  value - the value with which the promise is to be fulfilled
    # In case of errors (e.g. if $milliseconds is not an integer), the
    # promise is rejected with the 'reason' parameter set to an error
    # message and the 'edict' parameter set to a Tcl error dictionary.
    #
    # Also see [ptimeout] which is similar but rejects the promise instead
    # of fulfilling it.

    return [Promise new [lambda {millisecs value prom} {
        if {![string is integer -strict $millisecs]} {
            # We don't allow "idle", "cancel" etc. as an argument to after
            throw {PROMISE TIMER INVALID} "Invalid timeout value \"$millisecs\"."
        }
        after $millisecs [list promise::safe_fulfill $prom $value]
    } $millisecs $value]]
}

pworker [::promise]

promise, Top

Runs a script in a worker thread from a thread pool and returns a promise for the same.

pworker tpool script

Parameters
tpool thread pool identifier
script script to run in the worker thread
Return value

Returns a promise that will be settled by the result of the script

Description

Runs a script in a worker thread from a thread pool and returns a promise for the same.

The Thread package allows creation of a thread pool with the 'tpool create' command. The `pworker` command runs the specified script in a worker thread from a thread pool. The promise returned from this command will be fulfilled with the result of the script if it completes successfully. Otherwise, the promise will be rejected with an with the 'reason' parameter containing the error message and the 'edict' parameter containing the Tcl error dictionary from the script failure.

Note that $script is a standalone script in that it is executed in a new thread with a virgin Tcl interpreter. Any packages used by $script have to be explicitly loaded, variables defined in the the current interpreter will not be available in $script and so on.

proc ::promise::pworker {tpool script} {

    # Runs a script in a worker thread from a thread pool and
    # returns a promise for the same.
    #   tpool - thread pool identifier
    #   script - script to run in the worker thread
    # Returns a promise that will be settled by the result of the script
    #
    # The Thread package allows creation of a thread pool with the
    # 'tpool create' command. The `pworker` command runs the specified
    # script in a worker thread from a thread pool. The promise
    # returned from this command will be fulfilled with the result of
    # the script if it completes successfully.
    # Otherwise, the promise will be rejected with an
    # with the 'reason' parameter containing the error message
    # and the 'edict' parameter containing the Tcl error dictionary
    # from the script failure.
    #
    # Note that $script is a standalone script in that it is executed
    # in a new thread with a virgin Tcl interpreter. Any packages used
    # by $script have to be explicitly loaded, variables defined in the
    # the current interpreter will not be available in $script and so on.

    # No need for package require Thread since if tpool is passed to
    # us, Thread must already be loaded
    return [Promise new [lambda {tpool script prom} {
        set thread_script [string map [list %PROM% $prom %TID% [thread::id] %SCRIPT% $script] {
            set retcode [catch {%SCRIPT%} result edict]
            if {$retcode == 0 || $retcode == 2} {
                set response [list ::promise::safe_fulfill %PROM% $result]
            } else {
                set response [list ::promise::safe_reject %PROM% $result $edict]
            }
            thread::send -async %TID% $response
        }]
        tpool::post -detached -nowait $tpool $thread_script
    } $tpool $script]]
}

race [::promise]

promise, Top

Returns a promise that fulfills or rejects when any promise in the $promises argument is fulfilled or rejected.

race promises

Parameters
promises
Return value

Returns a promise that fulfills or rejects when any promise in the $promises argument is fulfilled or rejected.

Description
promises a list of Promise objects

The returned promise will fulfill and reject with the same value as the first promise in $promises that fulfills or rejects.

proc ::promise::race {promises} {

    # Returns a promise that fulfills or rejects when any promise
    # in the $promises argument is fulfilled or rejected.
    #   promises - a list of Promise objects
    # The returned promise will fulfill and reject with the same value
    # as the first promise in $promises that fulfills or rejects.
    set race_promise [Promise new [lambda {promises prom} {
        if {[llength $promises] == 0} {
            catch {throw {PROMISE RACE EMPTYSET} "No promises specified."} reason edict
            $prom reject $reason $edict
            return
        }
        # Use safe_*, do not directly call methods since $prom may be
        # gc'ed once settled
        foreach promise $promises {
            $promise done [list ::promise::safe_fulfill $prom ] [list ::promise::safe_reject $prom]
        }
    } $promises]]

    return $race_promise
}

race* [::promise]

promise, Top

Returns a promise that fulfills or rejects when any promise in the passed arguments is fulfilled or rejected.

race* args

Parameters
argsAdditional options.
Return value

Returns a promise that fulfills or rejects when any promise in the passed arguments is fulfilled or rejected.

Description
args list of Promise objects

This command is identical to the 'race' command except that it takes multiple arguments, each of which is a Promise object. See [race] for a description.

proc ::promise::race* {args} {

    # Returns a promise that fulfills or rejects when any promise
    # in the passed arguments is fulfilled or rejected.
    #   args - list of Promise objects
    # This command is identical to the 'race' command except that it takes
    # multiple arguments, each of which is a Promise object. See [race]
    # for a description.
    return [race $args]
}

safe_fulfill [::promise]

promise, Top

Fulfills the specified promise.

safe_fulfill prom value

Parameters
prom the [Promise] object to be fulfilled
value the fulfillment value
Return value

Returns 0 if the promise does not exist any more, else the return value from its [fulfill] method.

Description

Fulfills the specified promise.

This is a convenience command that checks if $prom still exists and if so fulfills it with $value.

proc ::promise::safe_fulfill {prom value} {

    # Fulfills the specified promise.
    #  prom - the [Promise] object to be fulfilled
    #  value - the fulfillment value
    # This is a convenience command that checks if $prom still exists
    # and if so fulfills it with $value.
    #
    # Returns 0 if the promise does not exist any more, else the return
    # value from its [fulfill] method.
    if {![info object isa object $prom]} {
        # The object has been deleted. Naught to do
        return 0
    }
    return [$prom fulfill $value]
}

safe_reject [::promise]

promise, Top

Rejects the specified promise.

safe_reject prom value edict

Parameters
prom the [Promise] object to be fulfilled
value see [Promise.reject]
edict(optional, default ) see [Promise.reject]
Return value

Returns 0 if the promise does not exist any more, else the return value from its [reject] method.

Description

Rejects the specified promise.

This is a convenience command that checks if $prom still exists and if so rejects it with the specified arguments.

proc ::promise::safe_reject {prom value {edict {}}} {

    # Rejects the specified promise.
    #  prom - the [Promise] object to be fulfilled
    #  value - see [Promise.reject]
    #  edict - see [Promise.reject]
    # This is a convenience command that checks if $prom still exists
    # and if so rejects it with the specified arguments.
    #
    # Returns 0 if the promise does not exist any more, else the return
    # value from its [reject] method.
    if {![info object isa object $prom]} {
        # The object has been deleted. Naught to do
        return
    }
    $prom reject $value $edict
}

then_chain [::promise]

promise, Top

Chains the promise returned by a [then] method call to another promise.

then_chain promise

Parameters
promise the promise to which the promise returned by [then] is to be chained
Description

Chains the promise returned by a [then] method call to another promise.

The [Promise.then] method is a mechanism to chain asynchronous reactions by registering them on a promise. It returns a new promise which is settled by the return value from the reaction, or by the reaction calling one of three commands - [then_fulfill], 'then_reject' or [then_chain]. Calling 'then_chain' chains the promise returned by the 'then' method that queued the currently running reaction to $promise so that the former will be settled based on the latter.

It is an error to call this command from outside a reaction that was queued via the [then] method on a promise.

proc ::promise::then_chain {promise} {

    # Chains the promise returned by a [then] method call to
    # another promise.
    #  promise - the promise to which the promise returned by [then] is
    #     to be chained
    #
    # The [Promise.then] method is a mechanism to chain asynchronous
    # reactions by registering them on a promise. It returns a new
    # promise which is settled by the return value from the reaction,
    # or by the reaction calling one of three commands - [then_fulfill],
    # 'then_reject' or [then_chain]. Calling 'then_chain' chains
    # the promise returned by the 'then' method that queued the currently
    # running reaction to $promise so that the former will be settled
    # based on the latter.
    #
    # It is an error to call this command from outside a reaction
    # that was queued via the [then] method on a promise.
    upvar #1 target_promise target_promise
    if {![info exists target_promise]} {
        set msg "promise::then_chain called in invalid context."
        throw [list PROMISE THEN FULFILL NOTARGET $msg] $msg
    }
    $target_promise chain $promise
}

then_fulfill [::promise]

promise, Top

Fulfills the promise returned by a [then] method call from within its reaction.

then_fulfill value

Parameters
value the value with which to fulfill the promise
Description

Fulfills the promise returned by a [then] method call from within its reaction.

The [Promise.then] method is a mechanism to chain asynchronous reactions by registering them on a promise. It returns a new promise which is settled by the return value from the reaction, or by the reaction calling one of three commands - 'then_fulfill', [then_reject] or [then_chain]. Calling 'then_fulfill' fulfills the promise returned by the 'then' method that queued the currently running reaction.

It is an error to call this command from outside a reaction that was queued via the [then] method on a promise.

proc ::promise::then_fulfill {value} {

    # Fulfills the promise returned by a [then] method call from
    # within its reaction.
    #  value - the value with which to fulfill the promise
    #
    # The [Promise.then] method is a mechanism to chain asynchronous
    # reactions by registering them on a promise. It returns a new
    # promise which is settled by the return value from the reaction,
    # or by the reaction calling one of three commands - 'then_fulfill',
    # [then_reject] or [then_chain]. Calling 'then_fulfill' fulfills
    # the promise returned by the 'then' method that queued the currently
    # running reaction.
    #
    # It is an error to call this command from outside a reaction
    # that was queued via the [then] method on a promise.

    # TBD - what if someone calls this from within a uplevel #0 ? The
    # upvar will be all wrong
    upvar #1 target_promise target_promise
    if {![info exists target_promise]} {
        set msg "promise::then_fulfill called in invalid context."
        throw [list PROMISE THEN FULFILL NOTARGET $msg] $msg
    }
    $target_promise fulfill $value
}

then_reject [::promise]

promise, Top

Rejects the promise returned by a [then] method call from within its reaction.

then_reject reason edict

Parameters
reason a message string describing the reason for the rejection.
edict a Tcl error dictionary
Description

Rejects the promise returned by a [then] method call from within its reaction.

The [Promise.then] method is a mechanism to chain asynchronous reactions by registering them on a promise. It returns a new promise which is settled by the return value from the reaction, or by the reaction calling one of three commands - [then_fulfill], 'then_reject' or [then_chain]. Calling 'then_reject' rejects the promise returned by the 'then' method that queued the currently running reaction.

It is an error to call this command from outside a reaction that was queued via the [then] method on a promise.

proc ::promise::then_reject {reason edict} {

    # Rejects the promise returned by a [then] method call from
    # within its reaction.
    #   reason - a message string describing the reason for the rejection.
    #   edict - a Tcl error dictionary
    # The [Promise.then] method is a mechanism to chain asynchronous
    # reactions by registering them on a promise. It returns a new
    # promise which is settled by the return value from the reaction,
    # or by the reaction calling one of three commands - [then_fulfill],
    # 'then_reject' or [then_chain]. Calling 'then_reject' rejects
    # the promise returned by the 'then' method that queued the currently
    # running reaction.
    #
    # It is an error to call this command from outside a reaction
    # that was queued via the [then] method on a promise.
    upvar #1 target_promise target_promise
    if {![info exists target_promise]} {
        set msg "promise::then_reject called in invalid context."
        throw [list PROMISE THEN FULFILL NOTARGET $msg] $msg
    }
    $target_promise reject $reason $edict
}

version [::promise]

promise, Top

version

proc ::promise::version {} {
    return 1.1.0
}

Classes

Promise [::promise]

promise, Top

catchRegisters reactions to be run when the promise is rejected.
chainChains the promise to another promise.
cleanupRegisters a reaction to be executed for running cleanup code when the promise is settled.
constructorConstructor for the class
destructorDestructor for the class
doneRegisters reactions to be run when the promise is settled.
fulfillFulfills the promise.
getdataReturns data previously stored through the setdata method.
nrefsReturns the current reference count.
refIncrements the reference count for the object.
rejectRejects the promise.
setdataSets a value to be associated with a key.
stateReturns the current state of the promise.
thenRegisters reactions to be run when the promise is settled and returns a new [Promise] object that will be settled by the reactions.
unrefDecrements the reference count for the object.
valueReturns the settled value for the promise.
constructor [::promise::Promise]

Promise, Top

Create a promise for the asynchronous operation to be initiated by $cmd.

::promise::Promise create cmd

Parameters
cmd a command prefix that should initiate an asynchronous operation.
Description

Create a promise for the asynchronous operation to be initiated by $cmd.

The command prefix $cmd is passed an additional argument - the name of this Promise object. It should arrange for one of the object's settle methods [fulfill], [chain] or [reject] to be called when the operation completes.

method constructor {cmd} {

    # Create a promise for the asynchronous operation to be initiated
    # by $cmd.
    # cmd - a command prefix that should initiate an asynchronous
    #  operation.
    # The command prefix $cmd is passed an additional argument - the
    # name of this Promise object. It should arrange for one of the
    # object's settle methods [fulfill], [chain] or
    # [reject] to be called when the operation completes.

    set _state PENDING
    set _reactions [list ]
    set _do_gc 0
    set _bgerror_done 0
    set _nrefs 0
    array set _clientdata {}

    # Errors in the construction command are returned via
    # the standard mechanism of reject.
    #
    if {[catch {
        # For some special cases, $cmd may be "" if the async operation
        # is initiated outside the constructor. This is not a good
        # thing because the error in the initiator will not be
        # trapped via the standard promise error catching mechanism
        # but that's the application's problem (actually pgeturl also
        # uses this).
        if {[llength $cmd]} {
            uplevel #0 [linsert $cmd end [self]]
        }
    } msg edict]} {
        my reject $msg $edict
    }
}
destructor [::promise::Promise]

Promise, Top

OBJECT destroy

method destructor {} {
    #
}
catch [::promise::Promise]

Promise, Top

Registers reactions to be run when the promise is rejected.

OBJECT catch on_reject

Parameters
on_reject command prefix for the reaction reaction to run if the promise is rejected. If unspecified or an empty string, no reject reaction is registered. The reaction is called with an additional argument which is the value with which the promise was settled.
Description

Registers reactions to be run when the promise is rejected.

This method is just a wrapper around [then] with the 'on_fulfill' parameter defaulting to an empty string. See the description of that method for details.

method catch {on_reject} {

    # Registers reactions to be run when the promise is rejected.
    #   on_reject - command prefix for the reaction
    #     reaction to run if the promise is rejected. If unspecified
    #     or an empty string, no reject reaction is registered. The
    #     reaction is called with an additional argument which is the
    #     value with which the promise was settled.
    # This method is just a wrapper around [then] with the
    # 'on_fulfill' parameter defaulting to an empty string. See
    # the description of that method for details.
    return [my then "" $on_reject]
}
chain [::promise::Promise]

Promise, Top

Chains the promise to another promise.

OBJECT chain promise

Parameters
promise the [Promise] object to which this promise is to be chained
Return value

Returns '0' if promise had already been settled and '1' otherwise.

Description

Chains the promise to another promise.

If the promise on which this method is called has already been settled, the method has no effect.

Otherwise, it is chained to $promise so that it reflects that other promise's state.

method chain {promise} {

    # Chains the promise to another promise.
    #   promise - the [Promise] object to which this promise is to
    #     be chained
    #
    # Returns '0' if promise had already been settled and '1' otherwise.

    #ruff
    # If the promise on which this method is called
    # has already been settled, the method has no effect.
    if {$_state ne "PENDING"} {
        return 0;
    }

    #ruff
    # Otherwise, it is chained to $promise so that it reflects that
    # other promise's state.
    if {[catch {
        $promise done [namespace code {my FulfillAttached}] [namespace code {my RejectAttached}]
    } msg edict]} {
        my reject $msg $edict
    } else {
        set _state CHAINED
    }

    return 1
}
cleanup [::promise::Promise]

Promise, Top

Registers a reaction to be executed for running cleanup code when the promise is settled.

OBJECT cleanup cleaner

Parameters
cleaner command prefix to run on settlement
Return value

Returns a new promise that is settled based on the cleaner

Description

Registers a reaction to be executed for running cleanup code when the promise is settled.

This method is intended to run a clean up script when a promise is settled. Its primary use is to avoid duplication of code in the `then` and `catch` handlers for a promise. It may also be called multiple times to clean up intermediate steps when promises are chained.

The method returns a new promise that will be settled as per the following rules.

  • if the cleaner runs without errors, the returned promise will reflect the settlement of the promise on which this method is called.
  • if the cleaner raises an exception, the returned promise is rejected with a value consisting of the error message and dictionary pair.
method cleanup {cleaner} {

    # Registers a reaction to be executed for running cleanup
    # code when the promise is settled.
    #   cleaner - command prefix to run on settlement
    # This method is intended to run a clean up script
    # when a promise is settled. Its primary use is to avoid duplication
    # of code in the `then` and `catch` handlers for a promise.
    # It may also be called multiple times
    # to clean up intermediate steps when promises are chained.
    #
    # The method returns a new promise that will be settled
    # as per the following rules.
    # - if the cleaner runs without errors, the returned promise
    #   will reflect the settlement of the promise on which this
    #   method is called.
    # - if the cleaner raises an exception, the returned promise
    #   is rejected with a value consisting of the error message
    #   and dictionary pair.
    #
    # Returns a new promise that is settled based on the cleaner
    set cleaner_promise [[self class] new ""]
    my RegisterReactions CLEANUP [list ::promise::_cleanup_reaction $cleaner_promise $cleaner]
    return $cleaner_promise
}
done [::promise::Promise]

Promise, Top

Registers reactions to be run when the promise is settled.

OBJECT done on_fulfill on_reject

Parameters
on_fulfill(optional, default ) command prefix for the reaction to run if the promise is fulfilled. reaction is registered.
on_reject(optional, default ) command prefix for the reaction to run if the promise is rejected.
Description

Registers reactions to be run when the promise is settled.

Reactions are called with an additional argument which is the value with which the promise was settled.

The command may be called multiple times to register multiple reactions to be run at promise settlement. If the promise was already settled at the time the call was made, the reactions are invoked immediately. In all cases, reactions are not called directly, but are invoked by scheduling through the event loop.

The method triggers garbage collection of the object if the promise has been settled and any registered reactions have been scheduled. Applications can hold on to the object through appropriate use of the [ref] and [unref] methods.

Note that both $on_fulfill and $on_reject may be specified as empty strings if no further action needs to be taken on settlement of the promise. If the promise is rejected, and no rejection reactions are registered, the error is reported via the Tcl 'interp bgerror' facility.

The method does not return a value.

method done {{on_fulfill {}} {on_reject {}}} {

    # Registers reactions to be run when the promise is settled.
    #  on_fulfill - command prefix for the reaction to run
    #    if the promise is fulfilled.
    #    reaction is registered.
    #  on_reject - command prefix for the reaction to run
    #    if the promise is rejected.
    # Reactions are called with an additional argument which is
    # the value with which the promise was settled.
    #
    # The command may be called multiple times to register multiple
    # reactions to be run at promise settlement. If the promise was
    # already settled at the time the call was made, the reactions
    # are invoked immediately. In all cases, reactions are not called
    # directly, but are invoked by scheduling through the event loop.
    #
    # The method triggers garbage collection of the object if the
    # promise has been settled and any registered reactions have been
    # scheduled. Applications can hold on to the object through
    # appropriate use of the [ref] and [unref] methods.
    #
    # Note that both $on_fulfill and $on_reject may be specified
    # as empty strings if no further action needs to be taken on
    # settlement of the promise. If the promise is rejected, and
    # no rejection reactions are registered, the error is reported
    # via the Tcl 'interp bgerror' facility.

    # TBD - as per the Promise/A+ spec, errors in done should generate
    # a background error (unlike then).

    my RegisterReactions FULFILLED $on_fulfill REJECTED $on_reject

    #ruff
    # The method does not return a value.
    return
}
fulfill [::promise::Promise]

Promise, Top

Fulfills the promise.

OBJECT fulfill value

Parameters
value the value with which the promise is fulfilled
Return value

Returns '0' if promise had already been settled and '1' if it was fulfilled by the current call.

Description

Fulfills the promise.

If the promise has already been settled, the method has no effect.

Otherwise, it is transitioned to the 'FULFILLED' state with the value specified by $value. If there are any fulfillment reactions registered by the [done] or [then] methods, they are scheduled to be run.

method fulfill {value} {

    # Fulfills the promise.
    #   value - the value with which the promise is fulfilled
    #
    # Returns '0' if promise had already been settled and '1' if
    # it was fulfilled by the current call.

    #ruff
    # If the promise has already been settled, the method has no effect.
    if {$_state ne "PENDING"} {
        return 0;             # Already settled
    }

    #ruff
    # Otherwise, it is transitioned to the 'FULFILLED' state with
    # the value specified by $value. If there are any fulfillment
    # reactions registered by the [done] or [then] methods, they
    # are scheduled to be run.
    set _value $value
    set _state FULFILLED
    my ScheduleReactions
    return 1
}
getdata [::promise::Promise]

Promise, Top

Returns data previously stored through the setdata method.

OBJECT getdata key

Parameters
key
Return value

Returns data previously stored through the setdata method.

Description
key key whose associated values is to be returned.

An error will be raised if no value is associated with the key.

method getdata {key} {

    # Returns data previously stored through the setdata method.
    #  key - key whose associated values is to be returned.
    # An error will be raised if no value is associated with the key.
    return $_clientdata($key)
}
nrefs [::promise::Promise]

Promise, Top

Returns the current reference count.

OBJECT nrefs

Return value

Returns the current reference count.

Description

Use for debugging only! Note, internal references are not included.

method nrefs {} {

    # Returns the current reference count.
    #
    # Use for debugging only! Note, internal references are not included.
    return $_nrefs
}
ref [::promise::Promise]

Promise, Top

Increments the reference count for the object.

OBJECT ref

Description

Increments the reference count for the object.

method ref {} {

    # Increments the reference count for the object.
    incr _nrefs
}
reject [::promise::Promise]

Promise, Top

Rejects the promise.

OBJECT reject reason edict

Parameters
reason a message string describing the reason for the rejection.
edict(optional, default ) a Tcl error dictionary
Return value

Returns '0' if promise had already been settled and '1' if it was rejected by the current call.

Description

Rejects the promise.

The $reason and $edict values are passed on to the rejection reactions. By convention, these should be of the form returned by the `catch` or `try` commands in case of errors.

If the promise has already been settled, the method has no effect.

Otherwise, it is transitioned to the 'REJECTED' state. If there are any reject reactions registered by the [done] or [then] methods, they are scheduled to be run.

If $edict is not specified, or specified as an empty string, a suitable error dictionary is constructed in its place to be passed to the reaction.

method reject {reason {edict {}}} {

    # Rejects the promise.
    #   reason - a message string describing the reason for the rejection.
    #   edict - a Tcl error dictionary
    #
    # The $reason and $edict values are passed on to the rejection
    # reactions. By convention, these should be of the form returned
    # by the `catch` or `try` commands in case of errors.
    #
    # Returns '0' if promise had already been settled and '1' if
    # it was rejected by the current call.

    #ruff
    # If the promise has already been settled, the method has no effect.
    if {$_state ne "PENDING"} {
        return 0;             # Already settled
    }

    #ruff
    # Otherwise, it is transitioned to the 'REJECTED' state.  If
    # there are any reject reactions registered by the [done] or
    # [then] methods, they are scheduled to be run.

    set _value $reason
    #ruff
    # If $edict is not specified, or specified as an empty string,
    # a suitable error dictionary is constructed in its place
    # to be passed to the reaction.
    if {$edict eq ""} {
        catch {throw {PROMISE REJECTED} $reason} - edict
    }
    set _edict $edict
    set _state REJECTED
    my ScheduleReactions
    return 1
}
setdata [::promise::Promise]

Promise, Top

Sets a value to be associated with a key.

OBJECT setdata key value

Parameters
key the lookup key
value the value to be associated with the key
Return value

Returns the value stored with the key.

Description

Sets a value to be associated with a key.

A promise internally maintains a dictionary whose values can be accessed with the [getdata] and [setdata] methods. This dictionary is not used by the Promise class itself but is meant to be used by promise library specializations or applications. Callers need to take care that keys used for a particular promise are sufficiently distinguishable so as to not clash.

method setdata {key value} {

    # Sets a value to be associated with a key.
    #  key - the lookup key
    #  value - the value to be associated with the key
    # A promise internally maintains a dictionary whose values can
    # be accessed with the [getdata] and [setdata] methods. This
    # dictionary is not used by the Promise class itself but is meant
    # to be used by promise library specializations or applications.
    # Callers need to take care that keys used for a particular
    # promise are sufficiently distinguishable so as to not clash.
    #
    # Returns the value stored with the key.
    set _clientdata($key) $value
}
state [::promise::Promise]

Promise, Top

Returns the current state of the promise.

OBJECT state

Return value

Returns the current state of the promise.

Description

The promise state may be one of the values 'PENDING', 'FULFILLED', 'REJECTED' or 'CHAINED'

method state {} {

    # Returns the current state of the promise.
    #
    # The promise state may be one of the values 'PENDING',
    # 'FULFILLED', 'REJECTED' or 'CHAINED'
    return $_state
}
then [::promise::Promise]

Promise, Top

Registers reactions to be run when the promise is settled and returns a new [Promise] object that will be settled by the reactions.

OBJECT then on_fulfill on_reject

Parameters
on_fulfill command prefix for the reaction to run if the promise is fulfilled. If an empty string, no fulfill reaction is registered.
on_reject(optional, default ) command prefix for the reaction to run if the promise is rejected. If unspecified or an empty string, no reject reaction is registered.
Return value

Returns a new promise that is settled by the registered reactions.

Description

Registers reactions to be run when the promise is settled and returns a new [Promise] object that will be settled by the reactions.

Both reactions are called with an additional argument which is the value with which the promise was settled.

The command may be called multiple times to register multiple reactions to be run at promise settlement. If the promise was already settled at the time the call was made, the reactions are invoked immediately. In all cases, reactions are not called directly, but are invoked by scheduling through the event loop.

If the reaction that is invoked runs without error, its return value fulfills the new promise returned by the 'then' method. If it raises an exception, the new promise will be rejected with the error message and dictionary from the exception.

Alternatively, the reactions can explicitly invoke commands [then_fulfill], [then_reject] or [then_chain] to resolve the returned promise. In this case, the return value (including exceptions) from the reactions are ignored.

If 'on_fulfill' (or 'on_reject') is an empty string (or unspecified), the new promise is created and fulfilled (or rejected) with the same value that would have been passed in to the reactions.

The method triggers garbage collection of the object if the promise has been settled and registered reactions have been scheduled. Applications can hold on to the object through appropriate use of the [ref] and [unref] methods.

method then {on_fulfill {on_reject {}}} {

    # Registers reactions to be run when the promise is settled
    # and returns a new [Promise] object that will be settled by the
    # reactions.
    #  on_fulfill - command prefix for the reaction to run
    #    if the promise is fulfilled. If an empty string, no fulfill
    #    reaction is registered.
    #  on_reject - command prefix for the reaction to run
    #    if the promise is rejected. If unspecified or an empty string,
    #    no reject reaction is registered.
    # Both reactions are called with an additional argument which is
    # the value with which the promise was settled.
    #
    # The command may be called multiple times to register multiple
    # reactions to be run at promise settlement. If the promise was
    # already settled at the time the call was made, the reactions
    # are invoked immediately. In all cases, reactions are not called
    # directly, but are invoked by scheduling through the event loop.
    #
    # If the reaction that is invoked runs without error, its return
    # value fulfills the new promise returned by the 'then' method.
    # If it raises an exception, the new promise will be rejected
    # with the error message and dictionary from the exception.
    #
    # Alternatively, the reactions can explicitly invoke commands
    # [then_fulfill], [then_reject] or [then_chain] to
    # resolve the returned promise. In this case, the return value
    # (including exceptions) from the reactions are ignored.
    #
    # If 'on_fulfill' (or 'on_reject') is an empty string (or unspecified),
    # the new promise is created and fulfilled (or rejected) with
    # the same value that would have been passed in to the reactions.
    #
    # The method triggers garbage collection of the object if the
    # promise has been settled and registered reactions have been
    # scheduled. Applications can hold on to the object through
    # appropriate use of the [ref] and [unref] methods.
    #
    # Returns a new promise that is settled by the registered reactions.

    set then_promise [[self class] new ""]
    my RegisterReactions  FULFILLED [list ::promise::_then_reaction $then_promise FULFILLED $on_fulfill]  REJECTED [list ::promise::_then_reaction $then_promise REJECTED $on_reject]
    return $then_promise
}
unref [::promise::Promise]

Promise, Top

Decrements the reference count for the object.

OBJECT unref

Description

Decrements the reference count for the object.

The object may have been destroyed when the call returns.

method unref {} {

    # Decrements the reference count for the object.
    #
    # The object may have been destroyed when the call returns.
    incr _nrefs -1
    my GC
}
value [::promise::Promise]

Promise, Top

Returns the settled value for the promise.

OBJECT value

Return value

Returns the settled value for the promise.

Description

The returned value may be the fulfilled value or the rejected value depending on whether the associated operation was successfully completed or failed.

An error is raised if the promise is not settled yet.

method value {} {

    # Returns the settled value for the promise.
    #
    # The returned value may be the fulfilled value or the rejected
    # value depending on whether the associated operation was successfully
    # completed or failed.
    #
    # An error is raised if the promise is not settled yet.
    if {$_state ni {FULFILLED REJECTED}} {
        error "Value is not set."
    }
    return $_value
}
Document generated by Ruff!
© 2018 Ashok P. Nadkarni