-
在
ES5
之后,JavaScript引入了Promise
,不需要浏览器的安排JavaScript引擎本身就可以发起任务了 -
宿主发起的任务称为宏任务
-
JavaScript引擎发起的任务称为微任务
-
同操作系统一样,等待的行为是一个事件循环,整个循环做的事情基本上就是反复的“等待—执行”
1.宏任务
-
在任务队列(task queue)中的函数
-
每次执行的过程都是一个宏观任务
-
宏任务的队列就相当于事件循环
-
宏任务中JavaScript的
Promise
还会产生异步代码,JavaScript需要这些异步代码在一个宏任务中完成,所以每个宏任务中又包含一个微任务队列 -
setTimeout
等宿主API
会添加宏任务
2.微任务
-
在作业队列(job queue)中的函数
-
微观务没有执行完成时不会进入下一个宏任务
-
Promise
永远在队列尾部添加微任务
3.Promise
-
Pormise
是JavaScript语言中标准化异步管理方式,总体思想是需要进行io
,等待或者其它异步操作的函数,不返回真实结果返回一个”承诺“,函数的调用方式可以在合适的时机选择等待这个承诺兑现,也就是利用Promise
的then
方法回调 -
通过几个例子来理解
Promise
,以下运行环境是浏览器var r = new Promise(function(resolve, reject){ console.log("a"); resolve() }); r.then(() => console.log("c")); console.log("b") // 打印结果是abc
- 先打印b再打印c是因为
Promise
的resolve
始终是异步操作,所以c无法出现在b之前 ,也就是说浏览器(宿主)按执行顺序执行代码时,遇到Promise.then()
产生了微任务,将该任务添加到了为任务队列中等待执行,然后继续执行代码,等打印出b后返回微任务队列中执行打印c
var r = new Promise(function(resolve, reject){ console.log("a"); resolve() }); setTimeout(()=>console.log("d"), 0) r.then(() => console.log("c")); console.log("b") // 打印结果abcd
- 这里多了一个
setTimeout()
,setTimeout()
不同于Promise
,它是浏览器的API
,产生的是宏任务,所以当执行到setTimeout()
的时候,将内部的函数添加到了宏任务队列中,当微任务队列中的c被打印结束后,执行宏任务中d的打印,因此d会打印到c的后面,这里可以理解微任务先于宏任务
- 先打印b再打印c是因为
-
通过例子理解微任务始终先于宏任务
setTimeout(()=>console.log("d"), 0) var r = new Promise(function(resolve, reject){ resolve() }); r.then(() => { var begin = Date.now(); while(Date.now() - begin < 1000); console.log("c1") new Promise(function(resolve, reject){ resolve() }).then(() => console.log("c2")) }); // 打印结果(998)c1 c2 d
- 代码可以看到,
setTimeout()
是先被放进了宏任务队列中,之后通过了一秒的执行while循环函数,并在一秒结束的时候通过Promise
创建了一个微任务放进了微任务队列中,但是打印结果中c2
是优先于d
被打印出来的,这里很好的说明了微任务优先的原理
- 代码可以看到,
4.异步执行的顺序分析
-
先分析宏任务的多少
-
再分析宏任务中微任务的多少
-
根据调用的次序,确定宏任务中的微任务次序
-
根据宏任务的触发规则和调用次序确定宏任务的执行次序
-
最后确定整个顺序
-
举一个例子分析一下:
function sleep(duration) { return new Promise(function(resolve, reject) { console.log("b"); setTimeout(resolve,duration); }) } console.log("a"); sleep(5000).then(()=>console.log("c")); // 打印结果abc
- 这段代码是利用
Promise
把setTimeout
封装成可以用于异步的函数 setTimeout
把整个代码分成了2个宏任务,与传入的时间duration
无关- 第一个宏任务中,包含先后同步执行的
console.log('a')
和console.log('b')
setTimeout
后,第二个宏观任务执行调用了resolve
,然后then
中的代码异步得到执行,所以调用了console.log('c')
- 这段代码是利用
5.async
和await
-
async
和await
是ES2016(ES7)
加入的新特性,它的运行时基础是Promise
,其中async
函数必定返回Promise
,利用await
关键字等待一个Promise
-
async
函数是可以嵌套的,定义了一批原子操作的情况下,可以利用async
函数组合出新的async
函数 -
通过一个实例理解一下:
function sleep(duration) { return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } async function foo(name){ await sleep(2000) console.log(name) } async function foo2(){ await foo("a"); await foo("b"); } foo2() // 打印结果a b
- 执行
foo2
函数后,两秒后打印出a,再过两秒后打印出b - 这里的
foo2
用await
调用了两次异步函数foo
,可以看到如果我们把sleep
这样的异步操作放入某一个框架或者库中,使用者无需了解Promise
的概念也可以进行异步编程,因为书写习惯上和同步编程更加贴合
- 执行
文章部分内容出自winter老师的《重学前端》,略加了一部分自己的拙见,欢迎学习讨论!