Promise in Tcl
Async http::geturl
proc callback_http_done {token} {
puts "http geturl done"
}
::http::geturl $url -command [list callback_http_done]
puts "wait http geturl"
vwait forever
恢复顺序
习惯上,我们的思维的习惯是线性的。对于上面的例子,比较贴近人们习惯的流程顺序是下面这样:
- 调用
::http::geturl
访问资源 - 访问成功的时候,执行下一步操作
但按照上面的代码结构,读到::http::geturl
这一行的时候,思维不是继续到下一行,而是需要跳转到其它地方去。
这个问题似乎可以通过把callback函数的定义挪个位置来解决。
inline 函数的定义
::http::geturl $url -command [lambda {token} {
puts "http geturl done"
}]
这样一来,思维的顺序就保留了。
嵌套地调用
如果在收到请求相应之后,需要根据情况开始一个新的异步请求,并且这种嵌套需要好几层。
::http::geturl $url -command [lambda {token} {
::http::geturl $new_url -command [lambda {token} {
::http::geturl $new_url -command [lambda {token} {
# ...
}] ;# callback 3
}] ;# callback 2
}] ;# callback 1
这种情况下,形成了一个所谓的调用嵌套,所谓的 callback pyramid of doom.
读代码读到最后一行的时候,这一行实际上对应的是第一个 callback,时间顺序上更靠后的 callback 反而需要往回去找。
用 promise 维持顺序
如果用 promise
的方式考虑这个问题,可以写成下面这个样子
set promise [promise new]
::http::geturl $url -command [list $promise resolve]
$promise done [lambda {token} {
# ...
}]
为了方便,把上面的代码封装一下
proc http_promise {url} {
set promise [promise new]
::http::geturl $url -command [list $promise resolve]
return $promise
}
set promise [http_promise $url]
$promise done [lambda {token} {
# ...
}]
用 promise 绑定多个 callback
用 promise 嵌套调用
set promise [http_promise $url_1]
set promise [$promise then [lambda {token} {
return [http_promise $new_url]
}]] ;# callback 1
set promise [$promise then [lambda {token} {
return [http_promise $new_url]
}]] ;# callback 2
$promise then [lambda {token} {
# ...
}] ;# callback 3
这下逻辑上,至少维持了时间上的顺序。
promise 的实现
oo::class create promise {
constructor {} {
set this(ready) 0
set this(callback) ""
}
method execute {} {
if {$this(ready) && $this(callback) ne ""} {
return [$this(callback) $this(result)]
}
}
method resolve {result} {
set this(result) $result
set this(ready) 1
return [my execute]
}
method done {command} {
set this(callback) $command
my execute
}
}
proc http_promise {url} {
set promise [promise new]
::http::geturl $url -command [list $promise resolve]
return $promise
}
set promise [http_promise $url]
$promise done [lambda {args} {
# ...
}]
让 promise 更通用
oo::class create promise {
method then {command} {
set this(promise) [promise new]
my done $command
return $promise
}
method execute {} {
if {$this(ready) && $this(callback) ne ""} {
set new_result [$this(callback) $this(result)]
if {$this(promise) ne ""} {
$this(promise) resolve $result
}
}
} ;# end method
}
method execute {} {
if {$this(ready) && $this(callback) ne ""} {
set new_result [$this(callback) $this(result)]
my
}
}
method process_result {result} {
if {$result is "promise"} {
set promise $result
switch -- [$promise status] "resolved" {
my next_promise [$promise result]
} "pending" {
$promise done [list $this(promise) resolve]
}
} else {
my next_promise $result
}
}
method next_promise {result} {
if {$this(promise) ne ""} {
$this(promise) resolve $result
}
}
用 promise 实现 callback list
set promise [http_promise $url_1]
set promise [$promise then [lambda {token} {
do something 1
}]] ;# callback 1
set promise [$promise then [lambda {token} {
do something 2
}]] ;# callback 2
$promise then [lambda {token} {
do something 3
}] ;# callback 3
这下逻辑上,至少维持了时间上的顺序。
Promise is a placeholder
set promise [::http::geturl $url]
$promise done [lambda {token} {
# ...
}]
promise 的行为像是不管三七二十一先占下一个位子,然后再决定在这个位子上放些什么。
asyn-command -callback $callback
asyn-command -callback [list $promise resolve]
trace add execution $callback-1 leave [list $callback-2] trace add execution $callback-2 leave [list $callback-3]
问题2:callback 的执行顺序
在一切正常的情况下,上面的例子的输出是左边这种情况呢,还是右边这种情况。
虽然较大可能(异步HTTP请求)的情况下,是左边这种情况。
但就普遍意义上来说,callback什么时候执行,是不一定的。比如 http 请求的实现可以是从cache读取数据,进而直接返回结果。
wait http geturl | http geturl done
http geturl done | wait http geturl
问题3: 代码重用和逻辑分离
回掉函数 callback_http_done
实际上可以分为两部分
- 数据的获取和清理
- 用户自定义的逻辑
其中“数据的获取和清理”这部分对于每次调用都是一样的,用户的处理逻辑则可能每次都不一样。
::http::geturl $url_1 -command [list callback_http_done_1]
::http::geturl $url_2 -command [list callback_http_done_2]
set promise $url_1 -command [list callback_http_done_1 $promise done callbackhttpdone