JavaScript异步操作

单线程模型

js只能一次执行一个任务(只在一个线程上运行,但js引擎其实有多个线程,只是都在后台配合),其它任务都必须在后面排队等待.

  • 优点: 实现起来比较简单 ,执行环境相对单纯
  • 缺点: 只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行

事件循环机制: CPU不管IO操作,挂起处于等待中的任务,去执行排在后面的任务; 当IO操作返回了结果 ,再把挂起的任务继续执行下去.

同步任务和异步任务

  • 同步任务 : 指那些没有被引擎挂起、在主线程上排队执行的任务. 只有前一个任务执行完毕,才能执行后一个任务。
  • 异步任务: 指那些被引擎挂起,不进入主线程而进入任务队列的任务。只有引擎觉得某个民步任务可以执行了,该任务(采用回调函数的形式)才会进入主线程执行。

Ajax可以当作同 步任务也可以当作异步任务处理。

任务队列和事件循环

“事件循环是一个程序结构,用于等待和发送消息和事件”

引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

异步操作的模式

回调函数

- 回调的写法
    function fun1(callback) {
      //...
      callback();
    }
    function fun2() {
      //...
    }
    fun1(fun2);

- 回调的优缺点
	* 优点:简单、容易理解和实现
	* 缺点:不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数


事件监听

 f1.on('done', f2);  //当f1发生done事件,就执行f2
 function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done'); // f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2
  }, 1000);
}

- 优缺点
	* 容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化
	* 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程

发布/订阅

事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。

 - 采用 Ben Alman 的 Tiny Pub/Sub实现 (jQuery的一个插件)
 	//  f2向信号中心jQuery订阅done信号
 	jQuery.subscribe('done', f2); 
	//jQuery.publish('done')的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行
	function f1() {
  	setTimeout(function () {
        // ...
        jQuery.publish('done');
      }, 1000);
    }
 //f2完成执行后,可以取消订阅(unsubscribe)
	jQuery.unsubscribe('done', f2);


异步操作的流程控制

如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序

串行执行

- 编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行

let items = [ 1, 2, 3, 4, 5, 6 ];
let results = [];

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

function series(item) {
  if(item) {
    async( item, function(result) {
      results.push(result);
      return series(items.shift());
    });
  } else {
    return final(results[results.length - 1]);
  }
}

series(items.shift());

//函数series就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final函数。items数组保存每一个异步任务的参数,results数组保存每一个异步任务的运行结果

并行执行

- 流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。

let items = [ 1, 2, 3, 4, 5, 6 ];
let results = [];

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

items.forEach(function(item) {
  async(item, function(result){
    results.push(result);
    if(results.length === items.length) {
      final(results[results.length - 1]);
    }
  })
});
// forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。

//,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。

串行与并行结合

- 并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。

let items = [ 1, 2, 3, 4, 5, 6 ];
let results = [];
let running = 0;
let limit = 2;

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

function launcher() {
  while(running < limit && items.length > 0) {
    var item = items.shift();
    async(item, function(result) {
      results.push(result);
      running--;
      if(items.length > 0) {
        launcher();
      } else if(running == 0) {
        final(results);
      }
    });
    running++;
  }
}

launcher();

//最多只能同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数

定时器

上一篇:ML之xgboost:利用xgboost算法(sklearn+7CrVa)训练mushroom蘑菇数据集(22+1,6513+1611)来预测蘑菇是否毒性(二分类预测)


下一篇:用Tinkercad学arduino之 红外遥控器