(点击
上方公众号
,可快速关注)
作者:
十年踪迹
www.h5jun.com/post/signals-and-async.html
如有好文章投稿,请点击 → 这里了解详情
我们知道,JavaScript 不管是操作 DOM,还是执行服务端任务,不可避免需要处理许多异步调用。在早期,许多开发者仅仅通过 JavaScript 的回调方式来处理异步,但是那样很容易造成异步回调的嵌套,产生 “Callback Hell”。
后来,一些开发者使用了 Promise 思想来避免异步回调的嵌套,社区将根据思想提出 Promise/A+ 规范,最终,在 ES6 中内置实现了 Promise 类,随后又基于 Promise 类在 ES2017 里实现了 async/await,形成了现在非常简洁的异步处理方式。
比如 thinkJS 下面这段代码就是典型的 async/await 用法,它看起来和同步的写法完全一样,只是增加了 async/await 关键字。
module
.
exports
=
class
extends
think
.
Controller
{
async indexAction
(){
let
model
=
this
.
model
(
'user'
);
try
{
await
model
.
startTrans
();
let
userId
=
await
model
.
add
({
name
:
'xxx'
});
let
insertId
=
await
this
.
model
(
'user_group'
).
add
({
user_id
:
userId
,
group_id
:
1000
});
await
model
.
commit
();
}
catch
(
e
){
await
model
.
rollback
();
}
}
}
async/await 可以算是一种语法糖,它将
promise
.
then
(
res
=>
{
do
sth
.
}).
catch
(
err
=>
{
some
error
})
转换成了
try
{
res
=
await promise
do
sth
}
catch
(
err
){
some
error
}
有了 async,await,可以写出原来很难写出的非常简单直观的代码:
function
idle
(
time
){
return
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
time
))
}
(
async
function
(){
//noprotect
do
{
traffic
.
className
=
'stop'
await idle
(
1000
)
traffic
.
className
=
'pass'
await idle
(
1500
)
traffic
.
className
=
'wait'
await idle
(
500
)
}
while
(
1
)
})()
上面的代码中,我们利用异步的 setTimeout 实现了一个 idle 的异步方法,返回 promise。许多异步处理过程都能让它们返回 promise,从而产生更简单直观的代码。
网页中的 JavaScript 还有一个问题,就是我们要响应很多异步事件,表示用户操作的异步事件其实不太好改写成 promise,事件代表控制,它和数据与流程往往是两个层面的事情,所以许多现代框架和库通过绑定机制把这一块封装起来,让开发者能够聚焦于操作数据和状态,从而避免增加系统的复杂度。
比如上面那个“交通灯”,这样写已经是很简单,但是如果我们要增加几个“开关”,表示“暂停/继续“和”开启/关闭”,要怎么做呢?如果我们还想要增加开关,人工控制和切换灯的转换,又该怎么实现呢?
有同学想到这里,可能觉得,哎呀这太麻烦了,用 async/await 搞不定,还是用之前传统的方式去实现吧。
其实即使用“传统”的思路,要实现这样的异步状态控制也还是挺麻烦的,但是我们的 PM 其实也经常会有这样麻烦的需求。
我们试着来实现一下:
function
defer
(){
let
deferred
=
{};
deferred
.
promise
=
new
Promise
((
resolve
,
reject
)
=>
{
deferred
.
resolve
=
resolve
deferred
.
reject
=
reject
})
return
deferred
}
class
Idle
{
wait
(
time
){
this
.
deferred
=
new
defer
()
this
.
timer
=
setTimeout
(()
=>
{
this
.
deferred
.
resolve
({
canceled
:
false
})
},
time
)
return
this
.
deferred
.
promise
}
cancel
(){
clearTimeout
(
this
.
timer
)
this
.
deferred
.
resolve
({
canceled
:
true
})
}
}
const
idleCtrl
=
new
Idle
()
async
function
turnOnTraffic
(){
let
state
;
//noprotect
do
{
traffic
.
className
=
'stop'
state
=
await
idleCtrl
.
wait
(
1000
)
if
(
state
.
canceled
)
break
traffic
.
className
=
'pass'
state
=
await
idleCtrl
.
wait
(
1500
)
if
(
state
.
canceled
)
break
traffic
.
className
=
'wait'
state
=
await
idleCtrl
.
wait
(
500
)
if
(
state
.
canceled
)
break
}
while
(
1
)
traffic
.
className
=
''
}
turnOnTraffic
()
onoffButton
.
onclick
=
function
(){
if
(
traffic
.
className
===
''
){
turnOnTraffic
()
onoffButton
.
innerHTML
=
'关闭'
}
else
{
onoffButton
.
innerHTML
=
'开启'
idleCtrl
.
cancel
()
}
}
上面这么做实现了控制交通灯的开启关闭。但是实际上这样的代码让 onoffButton、 idelCtrl 和 traffic 各种耦合,有点惨不忍睹……
这还只是最简单的“开启/关闭”,“暂停/继续”要比这个更复杂,还有用户自己控制灯的切换呢,想想都头大!
在这种情况下,因为我们把控制和状态混合在一起,所以程序逻辑不可避免地复杂了。这种复杂度与 callback 和 async/await 无关。async/await 只能改变程序的结构,并不能改变内在逻辑的复杂性。
那么我们该怎么做呢?这里我们就要换一种思路,让信号(Signal)登场了!看下面的例子:
class
Idle
extends
Signal
{
async wait
(
time
){
this
.
state
=
'wait'
const
timer
=
setTimeout
(()
=>
{
this
.
state
=
'timeout'
},
time
)
await
this
.
while
(
'wait'
)
clearTimeout
(
timer
)
}
cancel
(){
this
.
state
=
'cancel'
}
}
class
TrafficSignal
extends
Signal
{
constructor
(
id
){
super
(
'off'
)
this
.
container
=
document
.
getElementById
(
id
)
this
.
idle
=
new
Idle
()
}
get lightStat
(){
return
this
.
state
}
async pushStat
(
val
,
dur
=
0
){
this
.
container
.
className
=
val
this
.
state
=
val
await
this
.
idle
.
wait
(
dur
)
}
get canceled
(){
return
this
.
idle
.
state
===
'cancel'
}
cancel
(){
this
.
pushStat
(
'off'
)
this
.
idle
.
cancel
()
}
}
const
trafficSignal
=
new
TrafficSignal
(
'traffic'
)
async
function
turnOnTraffic
(){
//noprotect
do
{
await
trafficSignal
.
pushStat
(
'stop'
,
1000
)
if
(
trafficSignal
.
canceled
)
break