专栏名称: 前端大全
分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯
目录
相关文章推荐
前端大全  ·  裁员了,很严重,大家做好准备吧! ·  昨天  
前端之巅  ·  前端框架新格局:从过去一年的演进看未来趋势 ·  21 小时前  
黑龙江省商务厅  ·  听民意 汇民智:政府部门与代表委员的“高效联动” ·  20 小时前  
黑龙江省商务厅  ·  听民意 汇民智:政府部门与代表委员的“高效联动” ·  20 小时前  
庆阳市场监管  ·  第三届消费者权益保护微视频作品展播(二) ·  昨天  
庆阳市场监管  ·  第三届消费者权益保护微视频作品展播(二) ·  昨天  
安徽工信  ·  【数字化转型·安徽时刻】智能感知 ... ·  3 天前  
安徽工信  ·  【数字化转型·安徽时刻】智能感知 ... ·  3 天前  
51好读  ›  专栏  ›  前端大全

用信号来控制异步流程

前端大全  · 公众号  · 前端  · 2017-08-12 17:30

正文

(点击 上方公众号 ,可快速关注)


作者: 十年踪迹

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







请到「今天看啥」查看全文