Task A --> Task B --> Task C
html
宏任务:script(全局任务),setTimeout,setInterval,setImmediate,I/O,UI rendering
微任务:process.nextTick,Promise,Object.observer,MutaionObserver
执行栈:任务在执行栈上执行。前端
分三步:node
代码执行都是从script(全局任务)开始。web
Node.js采用V8
做为js的解析引擎,而I/O处理方面使用了本身设计的libuv,libuv
是一个基于事件驱动的跨平台抽象层,封装了不一样操做系统一些底层特性,对外提供统一的API,事件循环机制也是它里面的实现。
每次事件循环都包含了6个阶段:
1.timersajax
处理全部 setTimeout 和 setInterval 的回调。
这些回调被保存在一个最小堆(min heap) 中. 这样引擎只须要每次判断头元素, 若是符合条件就拿出来执行, 直到遇到一个不符合条件或者队列空了, 才结束 Timer Phase.
Timer Phase 中判断某个回调是否符合条件的方法:消息循环每次进入 Timer Phase 的时候都会保存一下当时的系统时间(T1),而后只要看上述最小堆中的回调函数设置的启动时间(T0)是否超过进入 Timer Phase 时保存的时间(T0<T1), 若是超过就拿出来执行.
Nodejs 为了防止某个 Phase 任务太多,每一个 Phase 执行回调都有个最大数量. 若是超过数量的话也会强行结束当前 Phase 而进入下一个 Phase. 这一条规则适用于消息循环中的每个 Phase.
2. I/O callbacksegmentfault
执行你的 fs.read, socket 等 IO 操做的回调函数, 同时也包括各类 error 的回调.
3.Idle, Prepare 阶段
内部使用,不讨论.
4. poll 阶段api
等待异步请求和数据。
首先会执行 watch_queue 队列中的 IO 请求, 一旦 watch_queue 队列空, 则整个消息循环就会进入 sleep , 从而等待被内核事件唤醒.
当js层代码注册的事件回调都没有返回的时候,事件循环会阻塞在poll阶段。看到这里,你可能会想了,会永远阻塞在此处吗?固然 Poll Phase 不能一直等下去.数组
Nodejs 就是经过 Poll Phase, 对 IO 事件的等待和内核异步事件的到达来驱动整个消息循环的.
5. check 阶段promise
这个阶段只处理 setImmediate 的回调函数.
6. close callback 阶段
专门处理一些 close 类型的回调. 好比 socket.on('close', ...). 用于资源清理.
总结:
1.Node.js 的事件循环分为6个阶段
2.浏览器和Node 环境下,microtask 任务队列的执行时机不一样
Node.js中,microtask 在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。
浏览器端,microtask 在事件循环的 macrotask 执行完以后执行浏览器
JavaScript是一种同步的、阻塞的、单线程的语言,在这种语言中,一次只能执行一个操做。但web浏览器定义了函数和API,容许咱们当某些事件发生时不是按照同步方式,而是异步地调用函数(好比,时间的推移,用户经过鼠标的交互,或者获取网络数据)。这意味着您的代码能够同时作几件事情,而不须要中止或阻塞主线程。
前端使用异步的场景有哪些?
setTimeout()指定的延迟时间以后来执行代码。
clearTimeout()取消setTimeout设置。
setInterval()每隔指定的时间执行代码
clearInterval()取消setInterval()设置
用addEventListener注册一个类型的事件的时候,浏览器会有一个单独的模块去接收这个东西,当事件被触发的时候,浏览器的某个模块,会把相应的函数扔到异步队列中,
回调函数callback(执行结束回来调用的函数)
定义:回调函数是一个函数,它做为参数传递给另外一个函数,并在父函数完成后执行。
请注意,不是全部的回调函数都是异步的,有一些是同步的。
如:Array.prototype.forEach()
forEach()
须要的参数是一个回调函数,回调函数自己带有两个参数,数组元素和索引值。它无需等待任何事情,当即运行。
const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus']; gods.forEach(function (eachName, index){ console.log(index + '. ' + eachName); });
一般所说的回调函数是指异步的。
回调函数的问题:
通常状况下是有异步操做时,使用Promise对这个异步操做进行封装。
Promise
有两种状态改变的方式,既能够从pending
转变为fulfilled
,也能够从pending
转变为rejected
。一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then
绑定的函数就会被调用。
resolve
和reject
new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }, error => { console.log(error); });
resolve函数的做用:在异步操做成功时调用,并将异步操做的结果做为参数传递出去;
reject函数的做用:在异步操做失败时调用,并将异步操做报出的错误做为参数传递出去。
promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch
所捕获。then
方法指定的回调函数,若抛出错误,也会被下一个catch
捕获。catch
中也能抛错,则须要后面的catch
来捕获。
then()有两个参数,分别为Promise从pending
变为fulfilled
和rejected
时的回调函数(第二个参数非必选)。这两个函数都接受Promise对象传出的值做为参数。
简单来讲,then
就是定义resolve
和reject
函数的,
该方法是.then(undefined, onRejected)
的别名,用于指定发生错误时的回调函数。
new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }, error => { console.log(error); }); //等同于 new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }).catch(error => { console.log(error); });
Promise.all(iterable)
该方法用于将多个Promise实例,包装成一个新的Promise实例。Promise.all
方法接受一个数组(或具备(迭代器)Iterator接口)做参数,数组中的对象(p一、p二、p3)均为promise实例(若是不是一个promise,该项会被用Promise.resolve
转换为一个promise)。它的状态由这三个promise实例决定。
fulfilled
,p的状态才会变为fulfilled
,并将三个promise返回的结果,按参数的顺序(而不是resolved
的顺序)存入数组,传给p的回调函数,如例3.8。rejected
,p的状态也会变为rejected
,并把第一个被reject
的promise的返回值,传给p的回调函数。这多个 promise 是同时开始、并行执行的,而不是顺序执行。
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(arr => { console.log(arr); }); //[ 'first', 'second', 'third' ]
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); //second
Promise.race(iterable)
Promise.race
方法一样接受一个数组(或具备Iterator接口)做参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled
或rejected
),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.race([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); //third
在第一个promise对象变为resolve后,并不会取消其余promise对象的执行
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("first"); console.log(1); }, 3000) }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("second"); console.log(2); }, 2000) }); let p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("third"); console.log(3); }, 1000) }); Promise.race([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); // 3 // third // 2 // 1
Promise.resolve('Success'); /*******等同于*******/ new Promise(function (resolve) { resolve('Success'); });
Promise.reject(new Error('error')); /*******等同于*******/ new Promise(function (resolve, reject) { reject(new Error('error')); });
建立promise的流程:
new Promise(fn)
或者它的快捷方式Promise.resolve()
、Promise.reject()
,返回一个promise对象fn
中指定异步的处理 resolve
reject
情景1:若是在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误
function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
Promise.resolve().then(() => { console.log('ok1'); throw 'throw error1' }).then(() => { console.log('ok2'); }, err => { // 捕获错误 console.log('err->', err); }).then(() => { // 该函数将被调用 console.log('ok3'); throw 'throw error3' }).then(() => { // 错误捕获前的函数不会被调用 console.log('ok4'); }).catch(err => { console.log('err->', err); }); //ok1 //err-> throw error1 //ok3 //err-> throw error3
console.log('script start'); async function async1() { console.log('async1'); await async2(); console.log('end'); } async function async2() { console.log('async2'); } setTimeout(function () { console.log('setTimeout'); }, 0); async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(function () { console.log('promise2'); }); console.log('script end'); //script start //async1 //async2 //promise1 //script end //end //promise2 //setTimeout
情景二:在异步回调中抛错,不会被catch
到
new Promise((resolve, reject) => { throw "error" }).then(data => { console.log(data); }).catch(err => { console.log(err); }); //error //对比 new Promise((resolve, reject) => { setTimeout(() => { throw "error"; }, 1000) }).then(data => { console.log(data); }).catch(err => { console.log(err); //This is never called });
优势:
缺点:
Promise一旦运行,不能终止掉。
Promise 加载一张图片
promise.js
function loadImg(src) { return new Promise((resolve, reject) => { let img = document.createElement("img"); img.src = src; img.onload = function (url) { resolve(img); }; img.onerror = function () { reject(err); } }); } const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'; const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'; loadImg(url1).then((img) => { console.log("图片1加载成功"); console.log(`图片1的宽度为:${img.width}`); }).then(() => { console.log("准备加载图片2"); return loadImg(url2); }).then((img) => { console.log("图片2加载成功"); console.log(`图片2的宽度为:${img.width}`); }).catch((err) => { console.log(err); });
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script src="./promise.js"></script> </html>
Generator解决了回调函数处理异步流程的第一个问题:不符合大脑顺序、线性的思惟方式。
// 使用setTimeout模拟异步 function ajax (url){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log(url + ' result.'); resolve(url + ' result.'); }, 100); }); } async function ajaxAsync () { var aResult = await ajax('/api/a'); console.log('aResult: ' + aResult); var bResult = await ajax('/api/b'); console.log('bResult: ' + bResult); } ajaxAsync();
参考文章:
http://www.javashuo.com/article/p-odwhqqel-bp.html
http://www.javashuo.com/article/p-ogqwcufq-go.html