Promise (v1.2.0)

IntroductionTop

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.

Download and installTop

The package is distributed as a single Tcl module and can be downloaded from the Sourceforge files area. It should be placed in any of the directories returned by the tcl::tm::path list command in your Tcl installation.

Alternately, after downloading to a temporary directory, you can install it with tclsh.

tclsh promise-VERSION.tm install

The package supports Tcl 8.6 and 9.x.

To load the package,

package require promise

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

::promiseTop

Promisespromise, Top

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:

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 promisespromise, Top

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 Promise::fulfill or Promise::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.

pconnectestablishes a socket client connection
pexecruns an external program collecting its output
pgeturlretrieves a URL using the http package
ptaskruns a script in a separate Tcl thread
ptimeoutrejects a promise when a timer expires
ptimerfulfills sa promise when a timer expires
pworkerruns a script in a Tcl thread pool

Settling promisespromise, Top

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 reactionspromise, Top

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 Promise.done method may be called multiple times and each reaction registered through it will be run when the promise is settled.

Chaining promisespromise, Top

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 ptimer 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 Promise.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 promisespromise, Top

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 uppromise, Top

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 Promise.ref and Promise.unref methods of a Promise object to explicitly manage its lifetime.

Commandspromise, Top

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
promisesa list of Promise objects
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.

Return value

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

proc ::promise::all {promises} {
    set all_promise [Promise new [lambda {promises prom} {
        set npromises [llength $promises]
        if {$npromises == 0} {
            $prom fulfill {}
            return
        }
        foreach promise $promises {
            $promise done  [list ::promise::_all_helper $prom $promise FULFILLED]  [list ::promise::_all_helper $prom $promise REJECTED]
        }
        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
argslist 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 value

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

proc ::promise::all* {args} {
    return [all $args]
}

async [::promise]promise, Top

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

async name paramdefs body
Parameters
namename of the procedure
paramdefsthe parameter definitions to the procedure in the same form as passed to the standard proc command
bodythe script to be executed
Description

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.

Return value

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

proc ::promise::async {name paramdefs body} {
    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
promthe promise to which the async promise is to be linked.
Description

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

Return value

Returns an empty string.

proc ::promise::async_chain {prom} {
    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
valthe value with which to fulfill the promise
Description

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

Return value

Returns an empty string.

proc ::promise::async_fulfill {val} {
    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
valthe value with which to reject the promise
edicterror dictionary for rejection Optional, default "".
Description

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

Return value

Returns an empty string.

proc ::promise::async_reject {val {edict {}}} {
    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
promthe promise that is to be waited on
Description

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

Return value

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

proc ::promise::await {prom} {
    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 dir ?args?
Parameters
dirNot documented.
proc ::promise::document {dir args} {
    variable _ruff_start_page
    package require ruff
    ::ruff::document [namespace current]  -includesource true  -hidesourcecomments true  -excludeprocs ^_.*  -autolink false  -recurse true  -outdir $dir  -outfile promise-[version].html  -titledesc "promise"  -copyright "[clock format [clock seconds] -format %Y] Ashok P. Nadkarni"  -version [version]  {*}$args  -preamble $_ruff_start_page
}

eventloop [::promise]promise, Top

Waits in the eventloop until the specified promise is settled.

eventloop prom
Parameters
promthe promise to be waited on
Description

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.

Return value

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

proc ::promise::eventloop {prom} {
    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
paramsparameter definitions for the procedure
bodybody of the procedures
argsadditional arguments to be passed to the procedure when it is invoked
Description

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 socket connection is completed.

pconnect ?args?
Parameters
argsarguments to be passed to the Tcl socket command
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.

Return value

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

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
argsprogram and its arguments as passed to the Tcl open call for creating pipes
Description

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

Return value

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

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

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 URL is fetched.

pgeturl url ?args?
Parameters
urlthe URL to fetch
argsarguments to pass to the http::geturl command
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.

Return value

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

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 ?edict?
Parameters
valuethe value with which to reject the promise
edicterror dictionary for rejection Optional, default "".
Description

By convention, $value should be of the format returned by Promise.reject.

Return value

Returns a new promise that is already rejected.

proc ::promise::prejected {value {edict {}}} {
    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
scriptscript to run in the thread
Description

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.

Return value

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

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
millisecstime interval in milliseconds
valuethe value with which the promise is to be rejected Optional, default Operation timed out..
Description

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 value

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

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

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 value

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

proc ::promise::ptimer {millisecs {value {Timer expired.}}} {
    return [Promise new [lambda {millisecs value prom} {
        if {![string is integer -strict $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
tpoolthread pool identifier
scriptscript to run in the worker thread
Description

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.

Return value

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

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
promisesa list of Promise objects
Description

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

Return value

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

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
argslist of Promise objects
Description

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 value

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

proc ::promise::race* {args} {
    return [race $args]
}

safe_fulfill [::promise]promise, Top

Fulfills the specified promise.

safe_fulfill prom value
Parameters
promthe Promise object to be fulfilled
valuethe fulfillment value
Description

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

Return value

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

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
promthe Promise object to be fulfilled
valuesee Promise.reject
edictsee Promise.reject Optional, default "".
Description

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

Return value

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

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 Promise.then method call to another promise.

then_chain promise
Parameters
promisethe promise to which the promise returned by Promise.then is to be chained
Description

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 Promise.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 Promise.then method call from within its reaction.

then_fulfill value
Parameters
valuethe value with which to fulfill the promise
Description

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 Promise.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 Promise.then method call from within its reaction.

then_reject reason edict
Parameters
reasona message string describing the reason for the rejection.
edicta Tcl error dictionary
Description

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 Promise.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.2.0
}

Classespromise, Top

Promise [::promise]promise, Top

Method summary
constructorConstructor for the class.
destructorDestructor for the class.
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.
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 create OBJNAME cmd
Promise new cmd
Parameters
cmda 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.
method constructor {cmd} {
    set _state PENDING
    set _reactions [list ]
    set _do_gc 0
    set _bgerror_done 0
    set _nrefs 0
    array set _clientdata {}
    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

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_rejectcommand 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

This method is just a wrapper around Promise.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
promisethe Promise object to which this promise is to be chained
Description

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.

Return value

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

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
cleanercommand prefix to run on settlement
Description

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.

Return value

Returns a new promise that is settled based on the cleaner

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_fulfillcommand prefix for the reaction to run if the promise is fulfilled. reaction is registered. Optional, default "".
on_rejectcommand prefix for the reaction to run if the promise is rejected. Optional, default "".
Description

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
valuethe value with which the promise is fulfilled
Description

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 Promise.done or Promise.then methods, they are scheduled to be run.

Return value

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

method fulfill {value} {
    if {$_state ne "PENDING"} {
        return 0;             # Already settled
    }
    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
keykey whose associated values is to be returned.
Description

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

Return value

Returns data previously stored through the setdata method.

method getdata {key} {
    return $_clientdata($key)
}

nrefs [::promise::Promise]Promise, Top

Returns the current reference count.

OBJECT nrefs
Description

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

Return value

Returns the current reference count.

method nrefs {} {
    return $_nrefs
}

ref [::promise::Promise]Promise, Top

Increments the reference count for the object.

OBJECT ref
method ref {} {
    incr _nrefs
}

reject [::promise::Promise]Promise, Top

Rejects the promise.

OBJECT reject reason ?edict?
Parameters
reasona message string describing the reason for the rejection.
edicta Tcl error dictionary Optional, default "".
Description

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 Promise.done or Promise.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.

Return value

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

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
}

setdata [::promise::Promise]Promise, Top

Sets a value to be associated with a key.

OBJECT setdata key value
Parameters
keythe lookup key
valuethe value to be associated with the key
Description

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.

Return value

Returns the value stored with the key.

method setdata {key value} {
    set _clientdata($key) $value
}

state [::promise::Promise]Promise, Top

Returns the current state of the promise.

OBJECT state
Description

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

Return value

Returns the current state of the promise.

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_fulfillcommand prefix for the reaction to run if the promise is fulfilled. If an empty string, no fulfill reaction is registered.
on_rejectcommand prefix for the reaction to run if the promise is rejected. If unspecified or an empty string, no reject reaction is registered. Optional, default "".
Description

Both reactions are passed the value with which the promise was settled. The reject reaction is passed an additional argument which is the error dictionary.

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.

Return value

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

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

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

Return value

Returns the settled value for the promise.

method value {} {
    if {$_state ni {FULFILLED REJECTED}} {
        error "Value is not set."
    }
    return $_value
}