异步:信号灯控制器

1,088 阅读2分钟

题目

使用Vue实现一个信号灯控制器,要求:

  1. 默认情况下

    1. 红灯亮20秒,并且最后闪烁5秒
    2. 绿灯亮20秒,并且最后闪烁5秒
    3. 黄灯亮10秒
  2. 灯的个数、颜色、持续时间、闪烁时间、灯光次序都可配置。如:

    [{color:'#fff',duration: 10000,twinkleDuration: 5000}, ...]

分析

可以从题目中拆分两个组件:

  1. 信号灯
  2. 控制器

信号灯需要做的事情:

  1. 接收配置信息。
  2. 亮、闪烁。
  3. 结束后通知控制器。

控制器需要做的事情:

  1. 接收配置信息,根据配置信息创建信号灯。
  2. 信号灯编排:接受信号灯的结束通知,并启动下一个信号灯。

难点:

  1. 信号灯的亮、闪烁。
  2. 信号灯结束,控制器启动下一个信号灯。

这里等待操作,显然需要通过异步来实现,我们可以先去复习Promise和async函数。

控制台版

function Light(color, duration, twinkleDuration) { // 信号灯
    let brightDuration = duration - twinkleDuration;
    function bright() {			// 亮
        return new Promise(function (resolve, reject) {
            console.log(`${color}灯开始亮`)
            setTimeout(resolve, brightDuration);
        })
    }
    function twinkle() {	// 闪烁
        return new Promise(function (resolve, reject) {
            console.log(`${color}灯开始闪烁`)
            setTimeout(resolve, twinkleDuration);
        })
    }
    function start() {	// 启动信号灯
        return new Promise(async function (resolve, reject) {
            await bright()				// ①
            await twinkle() 			// ②
            console.log(`${color}灯灭掉`)
            resolve()
        })
    }
    return {
        start
    }
}
function timer() { // 计时器,用来测试
    let start = new Date()
    let now = new Date()
    function next() {
        now = new Date()
        console.log(`第${Math.floor((now - start) / 1000)}秒`)
        setTimeout(next, 1000)
    }
    next()
}
async function LightControl(lightOptions) {	// 信号灯控制器
    for (let { color, duration, twinkleDuration } of lightOptions) {
        await Light(color, duration, twinkleDuration).start() 	// ③
    }
}

timer()
LightControl([
    { color: "红", duration: 4000, twinkleDuration: 2000, },
    { color: "绿", duration: 5000, twinkleDuration: 3000, },
    { color: "黄", duration: 6000, twinkleDuration: 4000, },
])

!!! 在①②③处,存在层次的回调,我们使用async函数来简化代码。

输出结果:
第0秒
红灯开始亮
第1秒
红灯开始闪烁
第2秒
第3秒
红灯灭掉
绿灯开始亮
第4秒
第5秒
绿灯开始闪烁
第6秒
第7秒
第8秒
绿灯灭掉
黄灯开始亮
第9秒
第10秒
黄灯开始闪烁
第11秒
第12秒
第13秒
第14秒
黄灯灭掉
第15秒
第16秒

输出符合预期,信号灯依次闪烁。

Vue版

下面,我们把代码稍微进行一下修改,让该信号灯显示在网页中。

// @/components/LightControl.vue

<template>
  <div>
    <Light v-for="(lightOption,index) of lightOptions"
           :lightOption="lightOption"
           :status="lightStatus[index]"
           :key="index"
    />
    <div>计时器:{{ time }}</div>
    <div>option: {{lightOptions}}</div>
  </div>
</template>

<script>
import Light from '@/components/Light'

export default {
  name: "LightControl",
  data: function () {
    return {
      lightOptions: [
        {color: "#FF0000", duration: 6000, twinkleDuration: 2000,},
        {color: "#00FF00", duration: 5000, twinkleDuration: 3000,},
        {color: "#FFFF00", duration: 6000},
      ],
      lightStatus: [0, 0, 0],
      time: 0,
    }
  },
  components: {
    Light
  },
  created() {
    this.lightControl(this.lightOptions)
    this.startTimer()
  },
  methods: {
    startTimer() {
      let start = new Date()
      let now = new Date()
      let that = this
      function next() {
        now = new Date()
        that.time = Math.floor((now - start) / 1000)
        setTimeout(next, 1000)
      }
      next()
    },
    changeStatus: function (i, value) {
      this.lightStatus.splice(i, 1, value)
    },
    createLight: function (color, duration, twinkleDuration=0, i) { // 信号灯
      let brightDuration = duration - twinkleDuration
      let that = this

      function bright() {			// 亮
        return new Promise(function (resolve) {
          that.changeStatus(i, 1)
          setTimeout(resolve, brightDuration);
        })
      }

      function twinkle() {	// 闪烁
        return new Promise(function (resolve) {
          that.changeStatus(i, 2)
          setTimeout(resolve, twinkleDuration);
        })
      }

      function start() {	// 启动信号灯
        return async function () {
          await bright()
          await twinkle()
          that.changeStatus(i, 0)
        }()
      }

      return {
        start
      }
    },
    lightControl: async function (lightOptions) {
      for (let i = 0; i < lightOptions.length; i++) {
        let {color, duration, twinkleDuration} = lightOptions[i]
        await this.createLight(color, duration, twinkleDuration, i).start()
      }
    }
  },
}
</script>

<style scoped>

</style>

// @/components/Light.vue

<template>
  <div :style="color" :class="lightClass">

  </div>
</template>

<script>
export default {
  name: "Light",
  props: {
    lightOption: {
      type: Object,
      value: null,
    },
    status: {
      type: Number,
      value: 0,
    },
  },
  computed: {
    lightClass: function () { // 传入的 props => 类名 => 样式
      let subClass = 'close'
      console.log(this.status)
      if (this.status === 1) {
        subClass = 'on'
      } else if (this.status === 2) {
        subClass = 'fade'
      }
      return ['light', subClass]
    }
  },
  data: function () {
    return {
      color: "#FFFF00",
    }
  },
  created() {
    this.color = {
      background: this.lightOption.color
    }
  },
}
</script>

<style scoped>
.light {
  display: inline-block;
  height: 30px;
  width: 30px;
  border-radius: 30px;
}

.on {
  opacity: 1;
}

.close {
  opacity: 0.2;
}

@keyframes fade {
  from {
    opacity: 1.0;
  }
  50% {
    opacity: 0;
  }
  to {
    opacity: 1.0;
  }
}

.fade {
  animation: fade 618ms infinite;
}

</style>

上述代码创建了两个Vue组件Light、LightControl,Light只是负责显示,控制逻辑都写在LightControl里。

效果如下图:

3

总结

使用JavaScript提供的Promise和async函数,我们可以非常方便等待异步函数

源码:gitee.com/litangmm/my…

创作不易!如果对你有帮助,还请点赞收藏。 如果有疑惑可以在下方留言。 如果有什么建议,可以私信我。