同步任务和异步任务

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

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

异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 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. Failed to set MokListRT: Invalid Parameter Something as gone seriously wrong: import_mok_state() failed: Invalid Parameter

    今天yum update升级centos7,重启后发现开不了机,报错如下: Failed to set MokListRT: Invalid ParameterSomething as gone se ...

  2. 使用 notify.js 桌面提醒

    //var iN = new iNotify({ // effect: 'flash', // interval: 500, // message: "有消息拉!", // aud ...

  3. PostMessage发送WM_CLOSE消息,实现关闭任意程序

    #include <Windows.h> #include <tchar.h> int WINAPI _tWinMain(HINSTANCE hInstance, HINSTA ...

  4. centos6 更新gcc glibc2.17

    curl -Lks http://www.hop5.in/yum/el6/hop5.repo > /etc/yum.repos.d/hop5.repo yum install gcc wget ...

  5. hibernate连接数据库和使用

    hibernate.cfg.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibe ...

  6. 文件 "c:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\ttt.mdf" 已压缩,但未驻留在只读数据库或文件组中。必须将此文件解压缩。 CREATE DATABASE 失败。无法创建列出的某些文件名。请查看相关错误。 (.Net SqlClient Data Provider)

    问题: 文件 "c:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\ttt.mdf" 已压缩,但 ...

  7. ES6中的解构赋值

    在解释什么是解构赋值前,我们先来看一下, ES5 中对变量的声明和赋值. var str = 'hello word'; 左边一个变量名,右边可以是字符串,数组或对象. ES6 中增加了一种更为便捷的 ...

  8. 网络-02-端口号-linux端口详解大全

    端口详解 1 tcpmux TCP Port Service Multiplexer 传输控制协议端口服务多路开关选择器  2 compressnet Management Utility compr ...

  9. How to using Piwis Tester II code Porsche rear end electronics

    V18.100 Piwis Tester II Diagnostic Tool For Porsche With CF30 Laptop High Quality Top 7 Reasons to G ...

  10. WSDL文件

    WSDL: <!--一次webservice调用,其实并不是方法调用,而是发送SOAP消息 ,即xml片段--> <!--以上一篇中的wsdl文档为例,这里我将注释写到文档中 --& ...