JavaScript异步模式


一.异步操作概述

JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。

注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合

同步任务和异步任务

程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。

同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。

异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。

任务队列和事件循环

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

扫描二维码关注公众号,回复: 4308634 查看本文章

事件循环:JavaScript 引擎不停地检查挂起来的异步任务,是不是可以进入主线程了

一.异步操作的模式

1.回调函数

function f1(callback) {
  // ...
  callback();
}

function f2() {
  // ...
}

f1(f2);
f2必须等到f1执行完成,才能执行

2.事件监听

另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

f1.on('done', f2);           //jquery语法
function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done');
  }, 1000);
}
f1发生done事件,就执行f2

3.发布/订阅

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

jQuery.subscribe('done', f2);
function f1() {
  setTimeout(function () {
    // ...
    jQuery.publish('done');
  }, 1000);
}
f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。
jQuery.unsubscribe('done', f2);    //也可以取消订阅

4.定时器

(1)setTimeout()

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行

var timer = setTimeout(function (a,b) {
  console.log(a + b);
}, 1000, 1, 1);         
//setTimeout共有4个参数。最后那两个参数,将在1000毫秒之后回调函数执行时,作为回调函数的参数


clearTimeout(timer);      //取消定时器

(2)setInterval()

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行

var i = 1
var timer = setInterval(function() {
  console.log(2);
}, 1000)
每隔1000毫秒就输出一个2,会无限运行下去


clearInterval(timer);      //取消定时器

(3)setTimeout(f, 0)

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,就无法等到当前脚本的同步任务,所以

定时器不会在当前的同步任务中执行。

setTimeout(f, 0)这种写法的目的是,尽可能早地执行f,但是并不能保证立刻就执行f

setTimeout(function () {
  console.log(1);
}, 0);
console.log(2);
// 2
// 1
2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。

整事件的发生顺序

setTimeout(f, 0)的作用:可以调整事件的发生顺序,让父元素的事件回调函数先发生

由于事件冒泡的原因,子元素的事件回调函数,会早于父元素

var input = document.getElementById('myButton');

input.onclick = function A() {
  setTimeout(function B() {
    input.value +=' input';
  }, 0)
};

document.body.onclick = function C() {
  input.value += ' body'
};
点击按钮后,先触发回调函数A,然后触发函数C。函数A中,setTimeout将函数B推迟到下一轮事件循环执行

5.Promise 对象

(1)Promise 的含义

Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

两个特点

(1)对象的三种状态[pending(进行中)、resolved(已成功)和rejected(已失败)]不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

(2)Promise 基本用法

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});
timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数

三.异步操作的流程控制

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

1.串行执行

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

var items = [ 1, 2, 3, 4, 5, 6 ];
var 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());        //用时6秒
函数series就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final函数。items数组保存每一个异步任务的参数,results数组保存每一个异步任务的运行结果。

2.并行执行

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

var items = [ 1, 2, 3, 4, 5, 6 ];
var 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) {            //用时1秒
  async(item, function(result){
    results.push(result);
    if(results.length === items.length) {
      final(results[results.length - 1]);
    }
  })
});
forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。

3.并行与串行的结合

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var 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();        //需要3秒,通过调节limit变量,达到效率和资源的最佳平衡
同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。

猜你喜欢

转载自blog.csdn.net/weixin_40778860/article/details/84501403
今日推荐