JavaScript:异步执行机制

使用JavaScript的开发者都知道,JS的异步执行机制在JS中占据着重要的地位,主要就是体现在回调函数以及事件方面,最近看了很多文章,将自己的一些感受和理解跟各位分享一下。

前面的博客中也有提到,JavaScript是一个单线程执行机制的程序,这样虽然说避免了并发访问的问题,但是这样也致使JS中的异步执行不能按照传统的多线程方式执行异步,JS所有的异步的实现需要插到同一个队列中,从而依次在主线中执行。一般来说,浏览器内存在主要的线程:JS执行引擎,HTTP线程和事件触发线程,JS内部的所有逻辑都需要在JS执行引擎中执行。我们先看一个问题:

setTimeout(function (args) {
    console.log('1')
}, 1000);

setInterval(function (args) {
    console.log('2')
}, 1000);

看上面的代码,我们知道timeout函数只会执行一次,而interval函数会执行多次,那么,JS的定时器函数是如何在JS中调用和处理呢?

其实,JS中的异步任务执行机制跟setTimeout函数和setInterval函数执行方式很类似,在实际开发中,难免会遇到大量的发送给后端的请求,最常见的就是Ajax请求,当在JS主线程运行过程中,当遇到xhr.send()请求时,主线程会立即向http线程(PS:在浏览器内包含一些常驻线程,分别是:渲染引擎线程,JS引擎线程,定时触发器线程,事件触发线程,异步http请求线程)发送指令,命令http线程向后台服务器发送请求,当JS主线程发送完这个指令后,这时的主线程不会等待这个Ajax任务的执行过程,而是继续沿着主线程的任务队列去执行下一个任务。等到http线程向服务器发送请求得到服务器的回复后,就会回到回调函数的队列中执行,当JS主线程中的所有同步任务执行完毕后,再来开始执行异步事件的回调。因为回调函数在JS中的等级是最低的,所以会排到最后来执行,就类似于setTimeout函数和setInterval函数。

实际上,JS中的定时器setTimeout函数会在初始化之后就开始执行它自身的定时任务,等定时的时间达到后,若此时的JS引擎没有被占用,就会直接执行定时任务,反之定时任务会进入到等待队列中等待时机执行。当进入JS的等待队列后,待JS引擎没有占用的时候再不断进行执行,借此来说,就是当JS引擎被占用时,则setTimeout会等待一段时间后再执行。

而setInterval函数,同样需要加入到JS的等待队列内,但是区别在于setInterval函数不会因进入等待队列而停止计时,如果当前一个setInterval函数没有执行的时候,又到了下一个setInterval函数,这时候,队列中就会包含两个setInterval函数的可能,如果一直累加,这样就会导致线程严重阻塞,不管从哪个角度来看这个,都是不允许的,所以,浏览器的JS就做了一个优化处理,当队列中存在setInterval函数时,这个setInterval函数不会进入到队列中(但是如果这个setInterval函数已经出队列并开始执行,还是会加入到队列)。所以,我们可以得到一个结论,在JS单线程的执行机制中,使用setInterval在JS的异步执行机制中的执行频率会比setTimeout更高,因为setTimeout会在一个执行完成后再执行下一个定时的任务,而setInterval时持续执行定时。

所以,所谓的JS异步请求的执行方式就是JS的主线程跟其余的辅助线程一起完成,当辅助线程执行完毕后JS的主线程任务队列就会将已经完成的辅助线程的回调插入到JS主线程任务队列的后面等待JS主线程的处理。

再来说下JS主要的几种异步处理方式:callback,promise,generator,async/await。

方式一:callback回调函数

上面我们所介绍的setTimeout函数就是一个最经典的回调函数的例子,虽说这种方式解决了JS同步的问题,但是缺点很明显,极易造成回调地狱现象(回调地狱是指在使用回调函数的时候层层嵌套,如果嵌套过多,会极大影响代码可读性和逻辑),而且不能使用try/catch捕获错误,同时也不能使用return。

方式二:promise构造函数

Promise构造函数是ES6中提出来的一种异步解决方案,Promise解决了回调等解决方案嵌套的问题并且使代码更加易读,有种在写同步方法的既视感,用于一个异步操作的最终完成(或最终失败)及其结果的表示。就相当于如果成功了则如何如何,如果失败了就如何如何,基本语法就是new Promise(参数),这里的参数指的是一个函数,函数中又包含两个参数:resolve和reject,其中resolve是成功回调函数,reject是失败的回调函数,而且resolve和reject需要通过.then(resolve,reject)的方法来执行。

var Promise = new Promise(function (resolve, reject) {
    if (true) {
        resolve('成功啦');
    } else {
        reject('失败啦');
    }
});
Promise.then(function (value) {
    console.log(value);
});
Promise.catch(function (reason) {
    console.log(reason);
});
Promise.finally(function () {
    console.log(3);
});

方式三:generator函数

generator函数也是ES6中新提出来的一种处理异步的函数,它有一个很大的特点,就是能够让函数在执行过程中暂停,基本语法如下:

function* generator() {
    yield '1';
    yield '2';
    return '3';
}

var g = generator();

g.next(); // {value: "1", done: false}
g.next(); // {value: "2", done: false}
g.next(); // {value: "3", done: true}

需要注意以下几点:

  • 在使用generator函数时,只需要在声明函数function后面加上*,同时配合函数内的yield关键字一起使用;
  • 在执行generator函数的时候,实际上是会返回一个iterator遍历器对象,然后通过next()方法,将函数内的代码根据yield关键字为界分步执行;
  • generator函数执行中,函数本身不会执行,实际上是在调用iterator遍历器对象中的next()方法,此时程序就会执行从前一个yield到下一个yield或者return之间的代码,并且会将yield后面的值,包装成json对象返回;
  • value取的yield或者return后面的值,否则就是undefined,done的值如果碰到return或者执行完成则返回true,否则返回false;

方式四:async/await

async/await方法就是万精油方式,需要注意的是:

  • async函数返回的是一个Promise对象,所以async函数执行后可以继续使用then等方式继续执行后面的逻辑;
  • await函数后面一般跟随的是Promsie对象,async函数执行时,遇到await,等待后面的Promise对象的状态从pending变成resolve的后,将resolve的参数返回并自动执行到下一个await或者结束;
  • await函数后面也可以跟一个async函数进行嵌套;

 

关于JavaScript执行异步执行机制就说到这儿,这部分的相关知识很多,这里也是简单的介绍,有不足的地方希望大家指出来,相互交流,相互学习!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值