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 class.

Constructing promises

Promises are constructed by creating instances of the 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 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 a list of Promise objects
Return value

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

Description

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} {
    set all_promise [Promise new [lambda {promises prom} {
        if {[llength $promises] == 0} {
            $prom fulfill {}
            return
        }
        set refed_promises {}
        try {
            foreach promise $promises {
                $promise ref
                lappend refed_promises $promise
            }
            set promises [lassign $promises first_promise]
            $first_promise done  [list ::promise::_all_helper $prom $first_promise $promises {} FULFILLED]  [list ::promise::_all_helper $prom $first_promise $promises {} REJECTED]
        } on error {msg edict} {
            foreach promise $refed_promises {
                $promise unref
                $promise done
            }
            return -options $edict $msg
        }
        } $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
args list of Promise objects
Return value

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

Description

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} {
    return [all $args]
}

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} {
    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]
}

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} {
    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
args arguments to be passed to the Tcl socket command
Return value

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

Description

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} {
    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} {
    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 the value with which to fulfill the created promise
Return value

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

Description

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

proc ::promise::pfulfilled {value} {
    return [Promise new [lambda {value prom} {
        $prom fulfill $value
    } $value]]
}

pgeturl [::promise]

promise, Top

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

pgeturl url args

Parameters
url the URL to fetch
args arguments to pass to the [http::geturl] command
Return value

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

Description

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} {
    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

Parameters
value the value with which to reject the promise
Return value

Returns a new promise that is already rejected

Description

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

proc ::promise::prejected {value} {
    return [Promise new [lambda {value prom} {
        $prom reject $value
    } $value]]
}

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} {
    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} {
                    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 time interval in milliseconds
value(optional, default Operation timed out.) the value with which the promise is to be rejected
Return value

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

Description

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

proc ::promise::ptimeout {millisecs {value {Operation timed out.}}} {
    return [Promise new [lambda {millisecs value prom} {
        if {![string is integer $millisecs]} {
            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 time interval in milliseconds
value(optional, default Timer expired.) the value with which the promise is to be fulfilled
Return value

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

Description

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

proc ::promise::ptimer {millisecs {value {Timer expired.}}} {
    return [Promise new [lambda {millisecs value prom} {
        if {![string is integer $millisecs]} {
            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} {
    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 a list of Promise objects
Return value

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

Description

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} {
    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
        }
        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
args list of Promise objects
Return value

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

Description

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

proc ::promise::race* {args} {
    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} {
    if {![info object isa object $prom]} {
        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 {}}} {
    if {![info object isa object $prom]} {
        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_promise]. 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} {
    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_promise]. 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} {
    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_promise]. 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} {
    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.0a2
}

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
nrefsReturns the current reference count
refIncrements the reference count for the object
rejectRejects the promise
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} {
    set _state PENDING
    set _reactions [list ]
    set _do_gc 0
    set _bgerror_done 0
    set _nrefs 0
    if {[catch {
        if {[llength $cmd]} {
            uplevel #0 [linsert $cmd end [self]]
        }
    } msg edict]} {
        my reject $msg $edict
    }
}
destructor [::promise::Promise]

Promise, Top

Destroys the object.

OBJECT destroy

Description

Destroys the object.

This method should not be generally called directly as Promise objects are garbage collected either automatically or via the ref and unref methods.

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} {
    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} {
    if {$_state ne "PENDING"} {
        return 0;
    }
    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. 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} {
    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 {}}} {
    my RegisterReactions FULFILLED $on_fulfill REJECTED $on_reject
    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} {
    if {$_state ne "PENDING"} {
        return 0;             # Already settled
    }
    set _value $value
    set _state FULFILLED
    my ScheduleReactions
    return 1
}
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 {} {
    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 {} {
    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 {}}} {
    if {$_state ne "PENDING"} {
        return 0;             # Already settled
    }
    set _value $reason
    if {$edict eq ""} {
        catch {throw {PROMISE REJECTED} $reason} - edict
    }
    set _edict $edict
    set _state REJECTED
    my ScheduleReactions
    return 1
}
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 {} {
    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 {}}} {
    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 {} {
    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

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

method value {} {
    if {$_state ni {FULFILLED REJECTED}} {
        error "Value is not set."
    }
    return $_value
}
Document generated by Ruff!
© 2016 Ashok P. Nadkarni