同步任务和异步任务

同步和异步操作的区别就是是否阻碍后续代码的执行。

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

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

在setTimeout的执行形式上来看,setTimeout是不会阻碍其后续代码的执行的。所以可以理解为setTimeout是异步操作。

单线程模式

JS是单线程的,但JS运行环境(Chrome浏览器)是多线程的。

GUI线程

GUI线程就是渲染页面的,他解析HTML和CSS,然后将他们构建成DOM树和渲染树就是这个线程负责的。

JS引擎线程

这个线程就是负责执行JS的主线程,前面说的"JS是单线程的"就是指的这个线程。大名鼎鼎的Chrome V8引擎就是在这个线程运行的。需要注意的是,这个线程跟GUI线程是互斥的。互斥的原因是JS也可以操作DOM,如果JS线程和GUI线程同时操作DOM,结果就混乱了,不知道到底渲染哪个结果。这带来的后果就是如果JS长时间运行,GUI线程就不能执行,整个页面就感觉卡死了。所以我们最开始例子的while(true)这样长时间的同步代码在真正开发时是绝对不允许的

定时器线程

前面异步例子的setTimeout其实就运行在这里,他跟JS主线程根本不在同一个地方,所以“单线程的JS”能够实现异步。JS的定时器方法还有setInterval,也是在这个线程。

事件触发线程

定时器线程其实只是一个计时的作用,他并不会真正执行时间到了的回调,真正执行这个回调的还是JS主线程。所以当时间到了定时器线程会将这个回调事件给到事件触发线程,然后事件触发线程将它加到任务队列里面去。最终JS主线程从任务队列取出这个回调执行。事件触发线程不仅会将定时器事件放入任务队列,其他满足条件的事件也是由他负责放进任务队列。

异步HTTP请求线程

这个线程负责处理异步的ajax请求,当请求完成后,他也会通知事件触发线程,然后事件触发线程将这个事件放入任务队列给主线程执行。

所以JS异步的实现靠的就是浏览器的多线程,当他遇到异步API时,就将这个任务交给对应的线程,当这个异步API满足回调条件时,对应的线程又通过事件触发线程将这个事件放入任务队列,然后主线程从任务队列取出事件继续执行。这个流程我们多次提到了任务队列,这其实就是Event Loop,下面我们详细来讲解下。

任务队列(Event Loop)

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供多个任务队列(根据任务的类型,所以有多个)。

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

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

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

目前JS的主要运行环境有两个——浏览器和Node.js。相比于V8引擎,node 中增加了两种异步方式: process.nextTick()  和 setImmediate()。

Node 规定,process.nextTick()Promise的回调函数,追加在本轮循环,即同步任务一旦执行完成,就开始执行它们。

setTimeoutsetIntervalsetImmediate的回调函数,追加在次轮循环

process.nextTick()

追加到本轮循环执行,而且是所有异步任务里面最快执行的,nextTickQueue。

Promise异步

function search(term) {
var url = 'http://example.com/search?q=' + term;
var xhr = new XMLHttpRequest();
var result; var p = new Promise(function (resolve, reject) {
xhr.open('GET', url, true);
xhr.onload = function (e) {
if (this.status === 200) {
result = JSON.parse(this.responseText);
resolve(result);
}
};
xhr.onerror = function (e) {
reject(e);
};
xhr.send();
}); return p;
} search('Hello World').then(console.log, console.error);

Promise的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务队列追加在process.nextTick队列的后面,也属于本轮循环。

process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
//
//
//
//

与正常任务比较:

setTimeout(function() {
console.log(1);
}, 0); new Promise(function (resolve, reject) {
console.log(4);
resolve(2);
}).then(console.log); console.log(3); // 4
// 3
// 2
// 1
// 注意: Promise构造函数中的代码为同步执行。

setTimeout与setImmediate

setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。这意味着,setTimeout指定的代码,必须等到本次事件循环执行的所有代码都执行完,才会执行。

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f,0),也不会立刻执行(放到任务队列后,还要等待主线程空闲且任务队列中在它之前没有未执行的回调)。

setTimeout(f,0)将第二个参数设为0,作用是让 f 在现有的任务(脚本的同步任务和“任务队列”中已有的事件)一结束就立刻执行。也就是说,setTimeout(f,0)的作用是,尽可能早地执行指定的任务。

var a = 1;
setTimeout(function(){
a = 2;
console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);//输出//1//3//2
//因为先执行同步代码
var flag = true;
setTimeout(function(){
flag = false;
},0)
while(flag){ }
console.log(flag);
//什么都不会输出,而且浏览器会出现卡死状态
//因为先执行同步代码,所以相当于一直在做while(true){}的无限循环。因此不会输出console.log(flag)也不会执行到异步

setTimeout在 timers 阶段执行,而setImmediate在 check 阶段执行(将下方,Nodejs事件循环的六个阶段)。但是,setTimeout不一定总是早于setImmediate完成。

console.log('outer');

setTimeout(() => {
console.log('setTimeout');
}, 0); setImmediate(() => {
console.log('setImmediate');
});

原因是,在nodejs中 setTimeout(fn, 0) 会被强制 setTimeout(fn, 1),详情见官方文档

 同步代码执行完毕,进入Event Loop
先进入times阶段,检查当前时间过去了1毫秒没有。如果过了1毫秒,满足setTimeout条件,执行回调;如果没过1毫秒,跳过
跳过空的阶段,进入check阶段,执行setImmediate回调

但如果 setTimeout 和 setImmediate 在同一个 setTimeout 中,因为已经在timers阶段,所以里面的setTimeout只能等下个循环了,所以setImmediate肯定先执行。同理的还有其他poll阶段的API也是这样的。

var fs = require('fs')

fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});

这里setTimeoutsetImmediatereadFile的回调里面,由于readFile回调是I/O操作,属于poll阶段,所以里面的定时器只能进入下个timers阶段,而setImmediate却可以在接下来的check阶段运行。所以setImmediate先运行,运行完后,再去检查timers,才会运行setTimeout

Nodejs事件循环的六个阶段

首先,事件循环是在主线程上完成的。其次,脚本开始执行时,事件循环只进行了初始化,并没有开始。只有当下面事件执行完后,事件循环才会开始

  • 同步任务
  • 发出异步请求
  • 规划定时器生效的时间
  • 执行 process.nextTick()等等

事件循环会无限次地执行,一轮又一轮。只有异步任务的回调函数队列清空了,才会停止执行。

每一轮的事件循环,分成六个阶段。

  1. timers:定时器阶段,处理setTimeout()setInterval()的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。
  2. I/O callbacks:除了以下操作的回调函数,其他的回调函数都在这个阶段执行。【setTimeout()setInterval()的回调函数;setImmediate()的回调函数;用于关闭请求的回调函数,比如socket.on('close', ...)】
  3. idle, prepare:这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。

    这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。

  4. poll:这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。
  5. check:该阶段执行setImmediate()的回调函数。
  6. close callbacks:该阶段执行关闭请求的回调函数,比如socket.on('close', ...)

每个阶段都有一个先进先出的回调函数队列。只有一个阶段的回调函数队列清空了,该执行的回调函数都执行了,事件循环才会进入下一个阶段。

转载自:https://www.imooc.com/article/71081

http://javascript.ruanyifeng.com/advanced/promise.html

单线程模型

作者:holdtom
链接:https://www.imooc.com/article/71081
来源:慕课网

单线程模型

作者:holdtom
链接:https://www.imooc.com/article/71081
来源:慕课网

setTimeout异步的更多相关文章

  1. 前端面试题和setTimeout异步

    var len=4; while(len--){ setTimeout(function(){ alert(len); },0); alert(len); } 这个题目的答案我先说出来,读者请仔细考虑 ...

  2. setTimeout异步加载

    两道经典的面试题,直接上代码 for(var i=0; i<3; i++){ setTimeout(function(){ i+=i; console.log(i); },1000) } var ...

  3. promise、async、await、settimeout异步原理与执行顺序

    一道经典的前端笔试题,你能一眼写出他们的执行结果吗? async function async1() { console.log("async1 start"); await as ...

  4. 前端开发:setTimeout与setInterval 定时器与异步循环数组

    前端开发:setTimeout与setInterval 定时器与异步循环数组 前言: 开通博客园三个月以来,随笔记录了工作中遇到的大大小小的难题,也看过无数篇令人启发的文章,我觉得这样的环境是极好的, ...

  5. 简单实现异步编程promise模式

    本篇文章主要介绍了异步编程promise模式的简单实现,并对每一步进行了分析,需要的朋友可以参考下 异步编程 javascript异步编程, web2.0时代比较热门的编程方式,我们平时码的时候也或多 ...

  6. js里的setTimeout和setInterval之后的页面是空白,阻塞浏览器的document对象,但是不阻塞script方法

    js里的setTimeout和setInterval是否进程阻塞? 阻塞浏览器的document对象,但是不阻塞script方法 当你在setTimeout中使用document.write时是不行的 ...

  7. JavaScript异步编程

    前言 如果你有志于成为一个优秀的前端工程师,或是想要深入学习JavaScript,异步编程是必不可少的一个知识点,这也是区分初级,中级或高级前端的依据之一.如果你对异步编程没有太清晰的概念,那么我建议 ...

  8. 你不知道的JavaScript--Item27 异步编程异常解决方案

    1.JavaScript异步编程的两个核心难点 异步I/O.事件驱动使得单线程的JavaScript得以在不阻塞UI的情况下执行网络.文件访问功能,且使之在后端实现了较高的性能.然而异步风格也引来了一 ...

  9. javascript的异步编程

    同步与异步 介绍异步之前,回顾一下,所谓同步编程,就是计算机一行一行按顺序依次执行代码,当前代码任务耗时执行会阻塞后续代码的执行. 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法后, ...

随机推荐

  1. linux发展

    硬件 1946年诞生于宾夕法尼亚州,占地170平米,重量达到30吨,名字叫做ENIAC(electronic numerical integrator and calculator)主要作用是为美国国 ...

  2. java中xxe漏洞修复方法

    java中禁止外部实体引用的设置方法不止一种,这样就导致有些开发者修复的时候采用的错误的方法 之所以写这篇文章是有原因的!最早是有朋友在群里发了如下一个pdf, 而当时已经是2019年1月末了,应该不 ...

  3. R-CNN,SPP-NET, Fast-R-CNN,Faster-R-CNN, YOLO, SSD, R-FCN系列深度学习检测方法梳理

    1. R-CNN:Rich feature hierarchies for accurate object detection and semantic segmentation 技术路线:selec ...

  4. win7插着网线开机卡死,拔下网线开机正常

    公司的部分win7电脑插着网线开机,进到桌面后网络图标转圈圈卡住.控制面板,启动项,任务管理器等都打不开.把网线拔下后再开机,电脑正常进入系统,后再插上网线就能正常上网了.被这个问题困扰了很久,百度也 ...

  5. java基础语法-内部类与匿名内部类

    1.成员内部类(声明在类内部&&方法之外) class Person{ String name = "韩梅梅"; int age; class Bird{ Stri ...

  6. Log4j2 HelloWorld

    Log4j2 使用教程 Log4j2 的好处就不在这里一一列举了,如果你搜了2,说明你对它一定有兴趣,并且想了解它,使用它. 一.下载log4j2 ,基本上你只需要导入下面两个jar包即可: log4 ...

  7. 如何在linux上查看tomcat的端口号

    1.先到tomcat配置文件查看tomcat的端口是什么,配置文件一般是:$CATALINA_HOME/conf/server这个文件,查找<Connector port="8080& ...

  8. 7个优秀的国内外移动端web框架(转)

    淘宝SUI Mobile框架   (light7框架 官网:http://www.light7.cn/)官网地址:http://m.sui.taobao.org/ SUI Mobile 是一套基于 F ...

  9. wpf中插入winform控件并获取句柄

    因工作需要使用wpf做界面,而有个开发包依赖picturebox控件,上网研究了一下,总算弄通了. 首先在项目中添加引用System.Windows.Forms与WindowsFormsIntegra ...

  10. IPv4的编址方法

    重难点: 1.IP地址有3位标识符,由ICANN进行分配. 2.两级的IP地址分为 ::={<网络号>,<主机号>}. 3.在同一个局域网上的主机或路由器的IP地址中的网络号必 ...