草庐IT

微信小程序JavaScript函数中的异步操作顺序执行

安布奇 2024-01-29 原文

1.前言

小程序开发中经常遇到后一个操作依赖前一个操作异步执行结果的情形。虽然JavaScript是单线程语言,但是主线程中的耗时操作通常都被放入任务队列中异步执行,避免阻塞主线程,例如:

let f1 = function (sequence) {
  console.log("f1开始执行");
  setTimeout(function () {
    console.log("f1执行完成");
  },10)
}
let f2 = function (sequence) {
  console.log("f2开始执行");
  setTimeout(function () {
    console.log("f2执行完成");
  },30)
}
let f3 = function (sequence) {
  console.log("f3开始执行");
  setTimeout(function () {
    console.log("f3执行完成");
  },1)
}
let test=function(){
  f1();
  f2();
  f3();
}

运行test函数发现执行结果为:

 可以看到函数f1,f2f3中的console部分按照test中的调用顺序依次执行,这是因为这部分代码是在主线程中执行的,是同步执行。而setTimeout的回调函数则未按照调用顺序依次执行,这是因为setTimeout被放入任务队列中异步执行,因为执行耗时f3<f1<f2,所以f3中的setTimeout最先从任务队列中返回到主线程中执行回调函数,而f2最后返回。

关于JavaScript执行顺序更多知识,参见:前端干货:JS的执行顺序 - 简书JS的运行机制 先来一个今日头条的面试题 1. 单线程的JavaScript js是单线程的,基于事件循环,非阻塞IO的。特点: 处理I/O型的应用,不适合CPU运算密集型...https://www.jianshu.com/p/62c7d633a879

 如果f2的执行依赖f1setTimeout回调函数的执行结果,而f3又依赖f2中的执行结果,则要保证三个函数中的异步操作顺序执行,例如:一个操作需要依次发出多个网络请求才能完成。这里提出一种非阻塞(主线程不用等待)方法,确保三个函数中的异步操作顺序执行。

2.使用轮询的方式确保异步操作顺序执行

为了避免小程序UI界面卡顿,不能采用阻塞主线程的方法,因此这里采用setInterval定时器实现异步轮询操作,确保异步操作顺序执行。首先定义顺序执行类:

let SequentialExec=class SequentialExec {
  constructor(func_list) {
      //顺序执行函数列表
      this.func_list = func_list;
      // 计数器
      this.count = 0;
      // 函数执行标志
      this.running = false;
      //函数执行结果
      this.res = null;
      //定时器序号
      this.timer=null;
  }
  /**
   * 启动定时器轮询
   */
  wait(){
    if(!this.timer){
    // 启动定时器轮询next方法
      this.timer=setInterval(this.next,5,this)
    }
  }
  /**
   * 切换运行函数
   * @param {顺序保持类对象} sequence 
   */
  next(sequence) {
       //执行完毕,关闭定时器
      if (sequence.count == sequence.func_list.length) {
        clearInterval(sequence.timer);
        return;
    }
      if (sequence.running == false) {
          try {
              sequence.running = true;
              //运行下一个函数
              sequence.func_list[sequence.count](sequence);
              sequence.count += 1;
              // 异步执行等待操作
              sequence.wait();
          } catch (error) {
              clearInterval(sequence.timer);
              throw error;
          }
      }
  }
}

使用示例

let f1 = function (sequence) {
  console.log("f1开始执行");
  setTimeout(function () {
    console.log("f1执行完成");
    sequence.running = false;
    sequence.res=1;
  }, 10)
}
let f2 = function (sequence) {
  console.log("f1执行结果",sequence.res)
  console.log("f2开始执行");
  setTimeout(function () {
    console.log("f2执行完成");
    sequence.running=false;
    sequence.res=2;
  }, 30)
}
let f3 = function (sequence) {
  console.log("f2执行结果",sequence.res)
  console.log("f3开始执行");
  setTimeout(function () {
    console.log("f3执行完成");
    sequence.running=false;
  }, 1)
}
let test = function () {
 let sequence=new SequentialExec([f1,f2,f3]);
 sequence.next(sequence);
}

运行test函数输出结果:

 原理

因为JavaScript的类对象作为参数传递时是浅拷贝(传址),所以可以用SequentialExec对象作为函数参数,用running属性记录异步操作的运行状态,用res属性保存异步操作结果,用count计数器决定执行的函数。在这里,SequentialExec类的对象sequence充当了func_list中函数的观察者,即采用了观察者模式

3.错误捕获

对于多个顺序执行的函数,如果其中一个函数出现错误,需要终止执行只需关闭定时器即可:

let SequentialExec=class SequentialExec {
  ....
 /**
   * 错误处理
   */
  error(error) {
    clearInterval(this.timer);
    this.running=false;
    console.log("出现错误,终止执行")
    throw error;
  }
  ....
}

例如执行f2出现错误: 

....
let f2 = function (sequence) {
  console.log("f1执行结果", sequence.res)
  console.log("f2开始执行");
  setTimeout(function () {
    //抛出错误,终止执行
    sequence.error("");
    console.log("f2执行完成");
    sequence.running = false;
    sequence.res = 2;
  }, 30)
}
....

 执行结果

4.注意事项

(1)上述方法适用于函数f1,f2和f3中只有一个异步函数的情形。

(2) sequence.running = false;必须在异步函数中调用,否则顺序执行不起作用。

5.总结

上述方法的优点是保持异步操作执行顺序的同时不会阻塞主线程,方便在多个异步操作中传递数据且处理错误比较方便。

但是缺点也很明显,就是这种方法是侵入式的,会产生大量sequence.running = false调用。

有关微信小程序JavaScript函数中的异步操作顺序执行的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  9. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  10. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

随机推荐