最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 用新号来控制异步流程

    正文概述 掘金(十年踪迹)   2020-12-29   443

    我们知道,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,可以写出原来很难写出的非常简单直观的代码: JS Bin 查看效果

    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 其实也经常会有这样麻烦的需求。

    我们试着来实现一下:

    JS Bin 查看效果

    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)登场了!看下面的例子:

    JS Bin 查看效果

    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
        await trafficSignal.pushStat('pass', 1500)
        if(trafficSignal.canceled) break
        await trafficSignal.pushStat('wait', 500)
        if(trafficSignal.canceled) break
      }while(1)
    
      trafficSignal.lightStat = 'off'
    }
    
    
    turnOnTraffic()
    
    onoffButton.onclick = function(){
      if(trafficSignal.lightStat === 'off'){
        turnOnTraffic()
        onoffButton.innerHTML = '关闭'
      } else {
        onoffButton.innerHTML = '开启'
        trafficSignal.cancel()
      }
    }
    

    我们对代码进行一些修改,封装一个 TrafficSignal,让 onoffButton 只控制 traficSignal 的状态。这里我们用一个简单的 Signal 库,它可以实现状态和控制流的分离,例如: JS Bin 查看效果

    const signal = new Signal('default')
    
    ;(async () => {
        await signal.while('default')
        console.log('leave default state')
    })()
    
    ;(async () => {
        await signal.until('state1')
        console.log('to state1')
    })()
    
    ;(async () => {
        await signal.until('state2')
        console.log('to state2')
    })()
    
    ;(async () => {
        await signal.until('state3')
        console.log('to state3')
    })()
    
    setTimeout(() => {
        signal.state = 'state0'
    }, 1000)
    
    setTimeout(() => {
        signal.state = 'state1'
    }, 2000)
    
    setTimeout(() => {
        signal.state = 'state2'
    }, 3000)
    
    setTimeout(() => {
        signal.state = 'state3'
    }, 4000)
    

    有同学说,这样写代码也不简单啊,代码量比上面写法还要多。的确这样写代码量是比较多的,但是它结构清晰,耦合度低,可以很容易扩展,比如: JS Bin 查看效果

    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
        if(dur) await this.idle.wait(dur)
      }
      get canceled(){
        return this.idle.state === 'cancel'
      }
      cancel(){
        this.idle.cancel()
        this.pushStat('off')
      }
    }
    
    const trafficSignal = new TrafficSignal('traffic')
    
    async function turnOnTraffic(){
      //noprotect
      do {
        await trafficSignal.pushStat('stop', 1000)
        if(trafficSignal.canceled) break
        await trafficSignal.pushStat('pass', 1500)
        if(trafficSignal.canceled) break
        await trafficSignal.pushStat('wait', 500)
        if(trafficSignal.canceled) break
      }while(1)
    
      trafficSignal.lightStat = 'off'
    }
    
    
    turnOnTraffic()
    
    onoffButton.onclick = function(){
      if(trafficSignal.lightStat === 'off'){
        turnOnTraffic()
        onoffButton.innerHTML = '关闭'
      } else {
        onoffButton.innerHTML = '开启'
        trafficSignal.cancel()
      }
    }
    
    turnRed.onclick = function(){
      trafficSignal.cancel()
      trafficSignal.pushStat('stop')
    }
    
    turnGreen.onclick = function(){
      trafficSignal.cancel()
      trafficSignal.pushStat('pass')
    }
    
    turnYellow.onclick = function(){
      trafficSignal.cancel()
      trafficSignal.pushStat('wait')
    }
    

    Signal 非常适合于事件控制的场合,再举一个更简单的例子,如果我们用一个按钮控制简单的动画的暂停和执行,可以这样写: JS Bin 查看效果

    let traffic = new Signal('stop')
    
    requestAnimationFrame(async function update(t){
      await traffic.until('pass')
      block.style.left = parseInt(block.style.left || 50) + 1 + 'px'
      requestAnimationFrame(update)
    })
    
    button.onclick = e => {
      traffic.state = button.className = button.className === 'stop' ? 'pass' : 'stop'
    }
    

    总结

    我们可以用 Signal 来控制异步流程,它最大的作用是将状态和控制分离,我们只需要改变 Signal 的状态,就可以控制异步流程,Signal 支持 until 和 while 谓词,来控制状态的改变。

    可以在 GitHub repo 上进一步了解关于 Signal 的详细信息。

    -- EOF --


    起源地下载网 » 用新号来控制异步流程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元