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.
The package supports Tcl 8.6 and 9.x.
To load the package,
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:
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 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).
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 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.
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
would print
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.
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
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.
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.
Parameters
promises | a 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.
Parameters
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 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.
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 |
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.
Parameters
prom | the 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.
Parameters
val | the 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.
Parameters
val | the value with which to reject the promise |
edict | error 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.
Parameters
prom | the 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.
Parameters
dir | Not 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.
Parameters
prom | the 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.
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
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.
Parameters
args | arguments 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.
Parameters
args | program 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.
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.
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.
Parameters
url | the URL to fetch |
args | arguments 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.
Parameters
value | the value with which to reject the promise |
edict | error 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.
Parameters
script | script 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.
Parameters
millisecs | time interval in milliseconds |
value | the 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.
Parameters
millisecs | time interval in milliseconds |
value | the 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.
Parameters
tpool | thread pool identifier |
script | script 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.
Parameters
promises | a 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.
Parameters
args | list 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.
Parameters
prom | the Promise object to be fulfilled |
value | the 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.
Parameters
prom | the Promise object to be fulfilled |
value | see Promise.reject |
edict | see 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.
Parameters
promise | the 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.
Parameters
value | the 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.
Parameters
reason | a message string describing the reason for the rejection. |
edict | a 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
proc ::promise::version {} { return 1.2.0 }
Classespromise, Top
Promise [::promise]promise, Top
Method summary
constructor | Constructor for the class. |
destructor | Destructor for the class. |
catch | Registers reactions to be run when the promise is rejected. |
chain | Chains the promise to another promise. |
cleanup | Registers a reaction to be executed for running cleanup code when the promise is settled. |
done | Registers reactions to be run when the promise is settled. |
fulfill | Fulfills the promise. |
getdata | Returns data previously stored through the setdata method. |
nrefs | Returns the current reference count. |
ref | Increments the reference count for the object. |
reject | Rejects the promise. |
setdata | Sets a value to be associated with a key. |
state | Returns the current state of the promise. |
then | Registers reactions to be run when the promise is settled and returns a new Promise object that will be settled by the reactions. |
unref | Decrements the reference count for the object. |
value | Returns the settled value for the promise. |
constructor [::promise::Promise]Promise, Top
Create a promise for the asynchronous operation to be initiated by $cmd
.
Promise new cmd
Parameters
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. |
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.
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.
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
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.
Parameters
promise | the 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.
Parameters
cleaner | command 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.
- 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.
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.
Parameters
on_fulfill | command prefix for the reaction to run if the promise is fulfilled. reaction is registered. Optional, default "" . |
on_reject | command 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.
Parameters
value | the 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.
Parameters
key | key 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.
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.
method ref {} { incr _nrefs }
reject [::promise::Promise]Promise, Top
Rejects the promise.
Parameters
reason | a message string describing the reason for the rejection. |
edict | a 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.
Parameters
key | the lookup key |
value | the 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.
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.
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 | command 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.
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.
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 }