浅谈js异步

大家都知道,js是一个单线程的语言(只有一个线程来执行js函数),所以如果某一个函数执行任务耗时比较长的话,就会造成阻塞,使得后续任务一直处于等待状态。

一、阻塞示例

        function f1(){
            for (var i = 0; i < 2000; i++) {
                    console.log('f1');
            }
        }
        function f2(){
            console.log('f2');
        }
        f1();
        f2();

f1、f2依次执行,控制台打印:

二、setTimeout函数

        function f1(){
            for (var i = 0; i < 2000; i++) {
                setTimeout(()=>{
                    console.log('f1');
                },500);
            }
        }
        function f2(){
            console.log('f2');
        }
        f1();
        f2();

f1、f2依次执行,控制台打印:

看上去好像先执行了f2,后续才执行了f1,其实不然,实际上依旧是先执行f1,再执行f2,没有实现异步,f1、f2依然是同步的(不要质疑,我说的是f1,f2没有实现异步)。

首先执行f1,利用for循环开启2000个定时器;

然后执行f2,打印字符串;

时间到了,2000个定时器触发。

注意:定时器到了时间触发之后,也并不是立即执行的,这要取决于js单线程内有没有还有没有函数正在运行,我们来看一个例子:

        function f8(){
            for (let i = 0; i < 2000; i++) {
                setTimeout(()=>{
                    console.log('f8'+i);
                },50);
            }
        }
        function f9(){
            for (let j = 0; j < 2000; j++) {
                    console.log('f9'+j);
            }
        }
        f8();
        f9();

我们看到,定时器的时间参数值很小,50ms,依然是依次执行f8、f9;

可以看到,虽然定时器先开启,并且第一个定时器在50ms之后,理当执行,但是此时发现,f9这个函数一直在执行任务,占据了js线程,所以这些定时器任务只好乖乖地等f9的循环走完,才开始执行。

所以说js声明的所有变量/函数都是同步的(ES6的Promise实例化之后可以有一个立即执行代码块,可视为同步任务);

但是我们可以通过各种方式开启一个异步任务(例如setTimeout函数的callback,Promise对象的then(callback)回调);

三、同步队列、异步队列

因为js是单线程的,所以任务会在线程上依次逐个执行;

在主线程上的任务队列,这些任务是同步的,他们会按先后依次执行;

另外还有一种任务队列,暂且称他们为异步任务队列,顾名思义,他们是异步任务,异步任务的特点是,必须等主线程上的任务没有了之后,才依次进入主线程执行。

来个例子感受一下:

ok,根据上图,我们可以知道,上述代码中,任务建立的顺序为:

同步任务:Promise实例化、f1的第一行打印、f1的for循环开启1000个定时器、5个f2的字符串打印;

异步任务:Promise的then回调、1000个定时器回调。

再根据之前的任务执行顺序分析,先不要看下面的执行结果,在心中想一下控制台的打印结果。

关于上述所提到的同步队列和异步队列(只是一种形象的说法),详情请戳下篇博客

四、异步实现

从上面我们可以发现,setTimeout(定时器)的回调是异步任务,这些个任务是直接放到异步任务队列中等待的。

我们熟知的ajax之所以是异步,完全得益于XMLHttpRequest这个异步对象。

ES6为方便异步编程,直接提供了一个异步对象Promise,使得异步编程变得简单。

原文地址:https://www.cnblogs.com/eco-just/p/10389927.html