JavaScript深入浅出异步编程一、setTimeout和setInterval

893 阅读3分钟

最近开发了一个适用于iOS上的混合开发库,支持JavaScript的开发,开发完以后对于JavaScript中的一些特性有了更加深入的了解。也就有了这篇文章,后续还会陆续写一些其他的关于JavaScript的文章。

异步编程一般跟多线程有关,而我们都知道JavaScript是单线程执行的,那何来异步之说?

事实上,但凡在JS中执行的代码都是单线程执行的。

然而,我们平常开发JavaScript的时候经常会提到异步编程的概念,而且在实际的开发过程中也经常会用到异步编程。那先说下我们是怎么用的,从最简单的setTimeout说起,然后一步一步的说到ES8中的asyncawait

setTimeout、setInterval

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

执行上面的代码1秒后输出timeout。下面换一种方式,使用setInterval来实现。

var interval = setInterval(function () {
    console.log('timeout');
    clearInterval(interval);
}, 1000);

上面两种方式实现的效果是一样的,都是在等待1秒后执行function中的代码。

看到上面的代码,但凡写过其他语言的比如:c++、java、OC等的,都知道这其实就是一个定时器,而setTimeout稍微特殊点,只会执行一次,而setInterval能够执行多次,直到显示取消为止。

举上面的例子主要是说明,在JavaScript中,setTimeoutsetInterval可以实现类似异步的效果。

不同的语言对于定时器的实现方式有可能不一样,甚至同一种语言,提供多种不同的定时器API,就拿Objectiv-c来说,起码有三种方式实现定时器的效果,但是不管什么语言,用什么方式来实现,工作原理差不多,

就是当定时器获取到某个信号的时候,在创建该定时器的线程中插入任务。这样的过程可以理解为线程调度。因为定时信号不一定在你回调的线程上触发的,该信号有可能直接由硬件中断产生,也有可能是软件模拟产生,但是在使用的时候都会在创建的线程获得回调。

因此,定时器本质上还是单线程的,跟异步编程没关系。我举个例子你就明白了

var t1 = new Date();
setTimeout(function(){
    var t2 = new Date();
    console.log('diff:'+(t2-t1));
},1000);

while (new Date() - t1 < 2000) {
}

上面的代码你觉得是输出1000呢?还是2000

实际的代码输出是diff:2000,具体是2000多少要看实际情况。举这样的例子说明什么呢,正是说明setTimeout还是串行执行的,你可以理解为见缝插针方式。因此setTimeout压根就算不上异步

一个残酷的现实是,javascript中不存在真的异步,一切异步都是假象。

所有的JS代码都是在同一个线程上执行的,因此不存在多线程的概念,也就不存在真正的异步编程

其实某些浏览器支持workerAPI,算是真的多线程。但是本篇不做介绍。

既然本篇的主题是异步编程,那么就算是假象,也得继续了。

XMLHttpRequest

我记得JS的异步编程的概念被提出来是从XMLHttpRequest(Ajax)开始的,而这里的异步指的只是实际的http请求是在非JS线程执行的,而请求完毕后的回调还是在JS线程执行的。这样看起来是异步的,但是我们要注意到,http异步请求不是在JS线程上执行的,而是由浏览器负责的,跟JS没关系。但是,正是因为有了这样的模型,才使得一些原本无法实现的功能变得可能。