Debug: setTimeout 使用做定时器时的错误函数传递方式

MarkTime: 2024-05-24 10:41:26

LogTime: 2024-11-10 14:55:53


首先复习

setTimeout():
语法: let timeId = setTimeout(func|code, [delay_millisecond])
说明: 延时器. 延迟delay_millisecond后, 执行参数1

setInterval(): 
语法: let timeId = setInterval(func|code, [delay_millisecond])
说明: 周期性执行器. 每隔delay_millisecond执行一次参数1

说明

问题

利用标识来判断是否执行下一次的 setTimeout函数,
原本写代码的时候想着的是:
     第一次的periodicTimerFunc() 执行后,
     如果1号还在录音,
     则等5s后开始下一次的 periodicTimerFunc() 执行
     但直接就执行了,
     并没有等待

其他补充

  • 版本相关:

    • vue: 2.6.14
    • js-audio-recorder: 1.0.7
  • 背景

    • 需求: 一边进行录音, 一边对录音的内容转义为中文输出到交互面板上

    • 说明:

      1. 以下代码  是当时写的 语音交互的第一版( js-audio-recorder1.0.7 ), 
        文档: https://recorder-api.zhuyuntao.cn/Recorder/event.html
        
        不支持 边转边录其实(没办法获取到中间数据), 
        所以先自己取巧了一下, 
        大体逻辑就是 启动完1号录音对象 再定时启动2号录音对象进行转录.
        
        存在明显 Bug (
          2号每次启动时, 
          会对录制正在进行中的1号造成干扰, 
          1号这个时候录制不到音频.
          => 1号 完成录制导出的录音文件: 听感: 每隔几秒就很有一段人体感受很不舒适的段静默
        )
        
      2. 查阅了下, 
        找到比较新的大大也还在维护的 recorder-core1.3.24040900,
        支持了在启动 recorder 录音对象之后的实时回调函数 onProcess().
        那么之前的Bug, 最后采取的
        优化思路:
          在onProcess里实现边录边转,
          并在最后导出文件的时候 把 段音频数组 合并即可.
        
        源码、说明文档: https://github.com/xiangyuecn/Recorder
        

源码

<script>
  /* 
    省略了很多, 只是留下基础结构为说明
  */
  import Recorder from 'js-audio-recorder';

  export default {
    data(){
      return {
        recorder: new Recorder({ // 1号录音对象
          sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
          sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000
          numChannels: 2, // 声道,支持 1 或 2, 默认是1
        }),
        pRecorder: new Recorder({ // 2号录音对象 - 用来在1号启动的期间, 把定量的音频传输转义
          sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
          sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000
          numChannels: 2, // 声道,支持 1 或 2, 默认是1
        }),
        isPeriodicTimerRun: false, // 是否启动周期调用识别
      }
    }, 
    methods: {
      /**
       * 录音周期转义文字
       */
      toPhoTranscri(){
        this.toStopPhoTranscri(); // 停止所有录音对象
        this.isPeriodicTimerRun = true;
        this.pRecorder.start();
        let cTime = '12345679';
        let periodicTimerFunc = (cTime) => {
          debugger
          console.log(cTime);

          let wavResource = this.pRecorder.getWAV();
          let mp3Resource = this.convertToMp3('pRecorder', wavResource);
          this.pRecorder.stop();
          this.pRecorder.start(); // TODO 重新启动会有延迟
          let currentTime = new Date().formatStr('yyyy-MM-dd HH:mm:ss');

          let formData = this.convertBlobToMultipartFile(mp3Resource, `录音${currentTime}`);
            
          // blahblahblah... 省略一堆把 wav转为mp3文件之后, 调用接口处理识别为中文后返回, 并后续渲染的逻辑
            
          if (this.isPeriodicTimerRun){ // isPeriodicTimerRun 是如果用户点击了"停止录音"的按钮之后会=false的控制变量
            debugger
            this.periodicTimer = setTimeout(periodicTimerFunc(currentTime), 5000);
          }
        }
        this.periodicTimer = setTimeout(periodicTimerFunc(cTime), 5000);
      },
      /**
       * 停止所有录音对象行为、一些初始化行为
       */
      toStopPhoTranscri(){ /* ... */ },
      /**
       * 将wav文件转换为mp3格式
       */
      convertToMp3(){ /* ... */ },
      /**
       * 将blob文件转换为form表单对象
       */
      convertBlobToMultipartFile(){ /* ... */ },
    }
  }
</script>


解决

嘿嘿我是笨蛋, 用来直接做参数传递肯定就直接一下子执行了啊, 会把执行的结果当作传参再延迟 5s 后执行.

所以需要改变一下写法,

让它在5s后再执行 periodicTimerFunc().

使用箭头函数, 去创建一个新的函数, 只是函数内部执行的是 periodicTimerFunc(),

这样子当定时器在 5s 后被调用的时候才会去执行函数内部的内容.

setTimeout(periodicTimerFunc(cTime), 5000) => setTimeout(() => periodicTimerFunc(cTime), 5000)


<script>
  import Recorder from 'js-audio-recorder';

  export default {
    data(){
      return {
        recorder: new Recorder({ // 1号录音对象
          sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
          sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000
          numChannels: 2, // 声道,支持 1 或 2, 默认是1
        }),
        pRecorder: new Recorder({ // 2号录音对象 - 用来在1号启动的期间, 把定量的音频传输转义
          sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
          sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000
          numChannels: 2, // 声道,支持 1 或 2, 默认是1
        }),
        isPeriodicTimerRun: false, // 是否启动周期调用识别
      }
    }, 
    methods: {
      /**
       * 录音周期转义文字
       */
      toPhoTranscri(){
        this.toStopPhoTranscri();
        this.isPeriodicTimerRun = true;
        this.pRecorder.start();
        let cTime = '';
        let periodicTimerFunc = (cTime) => {
          let wavResource = this.pRecorder.getWAV();
          let mp3Resource = this.convertToMp3('pRecorder', wavResource);
          this.pRecorder.stop();
          this.pRecorder.start();
          let currentTime = new Date().formatStr('yyyy-MM-dd HH:mm:ss');

          let formData = this.convertBlobToMultipartFile(mp3Resource, `录音${currentTime}`);
            
          // blahblahblah... 省略一堆把 wav转为mp3文件之后, 调用接口处理识别为中文后返回, 并后续渲染的逻辑

          if (this.isPeriodicTimerRun){
            this.periodicTimer = setTimeout(() => periodicTimerFunc(currentTime), 5000); // (ง •_•)ง 就是这里用箭头函数改一下
          }
        }
        
        this.periodicTimer = setTimeout(periodicTimerFunc(cTime), 5000); // 这里的确需要 periodicTimerFunc() 立刻执行, 即第一次启动入口
      },
      /**
       * 停止所有录音对象行为、一些初始化行为
       */
      toStopPhoTranscri(){ /* ... */ },
      /**
       * 将wav文件转换为mp3格式
       */
      convertToMp3(){ /* ... */ },
      /**
       * 将blob文件转换为form表单对象
       */
      convertBlobToMultipartFile(){ /* ... */ },
    }
  }
</script>


↓ 2024-05-24 10:41:26 至 2024-05-24 10:41:37 中间的时间过长是因为打了debugger断点被我人为中断了


总结

直接传递函数调用会导致函数立即执行, 并将结果传递给 setTimeout , 而不是传递函数本身, 所以应该传递的是返回函数调用的箭头函数, 即传递一个函数引用. 这样子 setTimeout 在延迟时间结束后才会执行被引用的函数.

posted @   LinForest_zZ  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示