第 8 题:setTimeout、Promise、Async/Await 的区别
baoyipin opened this issue · 33 comments
这题怎么没人答,我说下我粗浅的认识,抛砖引玉,欢迎指正和补充。
我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
宏观任务队列
微观任务队列
的区别
1. setTimeout
console.log('script start') //1. 打印 script start
setTimeout(function(){
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
2. Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
当JS主线程执行到Promise对象时,
-
promise1.then() 的回调就是一个 task
-
promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
-
promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
-
setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况
3. async/await
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
举个例子:
async function func1() {
return 1
}
console.log(func1())
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。
func1().then(res => {
console.log(res); // 30
})
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
上面的解释都很详细了。
我拿 babel es8 编译了下 async/await 结果是这样的
async function asyncTest() {
const ret = await asyncFunction();
}
--->
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function asyncTest() {
return _asyncTest.apply(this, arguments);
}
function _asyncTest() {
_asyncTest = _asyncToGenerator(function*() {
const ret = yield asyncFunction();
});
return _asyncTest.apply(this, arguments);
}
await/async 是通过 Generator/function* 来实现的。 所以 async/await 的相关优势也来自于generator。 Generator 是一个可以暂停 function ,感觉推出 generator 的目的包括不仅限于 Callback Hell 和 Inversion of Control。 感觉整个 community 有在往那个方向走。
function* generator(i) {
console.log('inside before')
yield i;
yield i + 10;
console.log('inside after')
}
var gen = generator(10);
console.log('outside before')
console.log(gen.next().value);
console.log(gen.next().value);
console.log('outside after')
gen.next();
结果如下
> "outside before"
> "inside before"
> 10
> 20
> "outside after"
> "inside after" // 如果不加最后一个gen.next(); 就不会有这一行
1. setTimeout
console.log('script start') //1. 打印 script start setTimeout(function(){ console.log('settimeout') // 4. 打印 settimeout }) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数 console.log('script end') //3. 打印 script start // 输出顺序:script start->script end->settimeout2. Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
console.log('script start') let promise1 = new Promise(function (resolve) { console.log('promise1') resolve() console.log('promise1 end') }).then(function () { console.log('promise2') }) setTimeout(function(){ console.log('settimeout') }) console.log('script end') // 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout当JS主线程执行到Promise对象时,
- promise1.then() 的回调就是一个 task
- promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
- promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
- setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况
3. async/await
async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start'); async1(); console.log('script end') // 输出顺序:script start->async1 start->async2->script end->async1 endasync 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
举个例子:
async function func1() { return 1 } console.log(func1())
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。func1().then(res => { console.log(res); // 30 })
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
更多可见setTimeout、Promise、Async/Await
什么叫同步的立即执行函数?建议大佬看看规范中Promise的定义。
有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?
有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?
会返回一个 被reject 的promise对象。继续绑定catch函数可以捕获到
async1 start
写的很好,感谢大大的分享,我还有一个疑惑上面提到的Async/Await await执行的代码 ,会跳出线程,但是await上面的打印会出现在await执行打印的前面,意思是await前面的代码都会跳出线程(异步执行),只有await后面的代码才是同步执行这个意思吗?
宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的
宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的
宏观吧,我其实就是比较奇怪,await前面的代码都跳出了线程吗,还是单纯就await 后面的执行跳出了线程, 也可能是我表达有问题, 为啥await以及前面代码的都会先打印 然后才是跳出线程执行外面的,最后才执行await下面的代码。
宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的
宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的
宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出
宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的
宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出
async/await不是同步了嘛,我也缕的不是很清楚,第10题那个回答挺好的
2. 基础知识
需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!
js运行原理
首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址
Microtask、Macrotask/Task
Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址。
看博客注意事项
-
博客内有带执行步骤的动画,一定要亲自点一下:star:
-
博客中将Macrotask叫做Task;
-
分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了
Microtask和Macrotask所包含的api:
Microtask
- process.nextTick
- promise
- Object.observe (废弃)
- MutationObserver
Macrotask
- setTimeout
- setImmediate
- setInterval
- I/O
- UI 渲染
如果不想看英文博客,我在这里作一点简单的总结:
- 在执行上下文栈的同步任务执行完后;
- 首先执行Microtask队列,按照队列
先进先出
的原则,一次执行完所有Microtask队列任务; - 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
- 为空则执行下一个Macrotask/Task;
- 不为空则执行Microtask
宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的
宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出
几乎同样的疑问,,,我搞不懂为什么await下面的代码会在script end之后输出,,我以为是在async2执行完就会执行的 然后才执行 script end,,,,不太懂
2. 基础知识
需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!
js运行原理
首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址Microtask、Macrotask/Task
Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址。
看博客注意事项
- 博客内有带执行步骤的动画,一定要亲自点一下star
- 博客中将Macrotask叫做Task;
- 分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了
Microtask和Macrotask所包含的api:
Microtask - process.nextTick - promise - Object.observe (废弃) - MutationObserver Macrotask - setTimeout - setImmediate - setInterval - I/O - UI 渲染
如果不想看英文博客,我在这里作一点简单的总结:
- 在执行上下文栈的同步任务执行完后;
- 首先执行Microtask队列,按照队列
先进先出
的原则,一次执行完所有Microtask队列任务;- 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
- 为空则执行下一个Macrotask/Task;
- 不为空则执行Microtask
文章中的例子确实写错了
2. 基础知识
需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!
js运行原理
首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址
这个是核心**的截图
Microtask、Macrotask/Task
Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址。
看博客注意事项
- 博客内有带执行步骤的动画,一定要亲自点一下star
- 博客中将Macrotask叫做Task;
- 分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了
Microtask和Macrotask所包含的api:
Microtask - process.nextTick - promise - Object.observe (废弃) - MutationObserver Macrotask - setTimeout - setImmediate - setInterval - I/O - UI 渲染
如果不想看英文博客,我在这里作一点简单的总结:
- 在执行上下文栈的同步任务执行完后;
- 首先执行Microtask队列,按照队列
先进先出
的原则,一次执行完所有Microtask队列任务;- 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
- 为空则执行下一个Macrotask/Task;
- 不为空则执行Microtask
文章中的的结果确实写错了,现在已经更正.谢谢你的指正! 如果对你造成误导,非常抱歉!
这题怎么没人答,我说下我粗浅的认识,抛砖引玉,欢迎指正和补充。
我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
第一次执行栈的同步任务都完成后,接着处理的应该是微任务吧,然后再从宏任务队列里拿一条宏任务到执行栈中,等执行栈中的宏任务处理完,再去清空微任务队列。
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
return 'async2';
// return Promise.resolve('async2');
}
async1();
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log('Promise then');
});
async函数本身返回是一个promise,为什么async2显式返回promise和不显式返回,两行打印顺序不一致,什么原理?显式返回顺序:Promise then --> async1 end;返回普通值顺序:async1 end --> Promise then
setTimeout 和promise 好理解, 宏任务队列和微任务队列。,
await fun() \n balabala..
await返回一个promise 后执行 balabala 所以我的理解就像使用了.then(()=>balabala),也是进入的微任务队列。 这样执行的顺序也就明白了
有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?
finally也是返回一个promise,自然可以用catch捕获运行时错误。
async function async1() { await async2(); console.log('async1 end'); } async function async2() { return 'async2'; // return Promise.resolve('async2'); } async1(); new Promise(function(resolve) { resolve(); }).then(function() { console.log('Promise then'); });
async函数本身返回是一个promise,为什么async2显式返回promise和不显式返回,两行打印顺序不一致,什么原理?显式返回顺序:Promise then --> async1 end;返回普通值顺序:async1 end --> Promise then
return ‘async2’时,执行代码相当于如下:
new Promise(function (resolve) {
resolve('async2');
}).then(function() {
console.log('async1 end');
});
console.log('async1 end');排在microtask queue前面,所以输出顺序是async1 end --> Promise then
return Promise.resolve('async2')时,执行代码相当于如下:
new Promise(function (resolve) {
resolve(Promise.resolve('async2'));
}).then(function() {
console.log('async1 end');
});
此时的then取决于里层promise的状态,console.log('async1 end')会排在microtask queue后面,所以输出顺序是Promise then --> async1 end
自己回答一波,若有错误请各位指出,互相学习!谢谢 ~
鉴于上面各位大佬回答的,其实这题主要还是考浏览器的EVENT LOOP
setTimeout属于宏任务,Promise属于微任务.而async和await其实是geneorator的语法糖,实质上最后返回的也是promise,所以我将其归为微任务(有错的,请指出,谢谢)。
而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。而Task队列中,我们又再之前提及到分:微任务和宏任务
微任务的优先级大于宏任务,所以在执行栈为空的时候,首先会去执行Micortask(微任务)队列,执行完毕后再去取Macrotask(宏任务)队列去执行栈中执行,一次执行一个,再去检查Micortask(微任务),若存在则执行Microtask,若没有则取下一个Macrotask任务继续执行,直至为空。
以上,是我对EventLoop的回答,但是我还是想在此基础上回答下关于这三个的异步不同之处。
setTimeout
setTimeout的异步使用方法算是比较古老的回调函数方式,就是我们之前写Jquery的时候,ajax的最常见的使用方式,这种的好处在于用很简单的方式实现了异步的方式,从而解决了异步直肠子的问题(耗时任务,一直处于等待)。缺点:回调地狱,这是写了多年Jq的一直很恶心的地方,代码嵌套太多,牵一发而动全身。
Promise
优点:解决了回调函数的问题,可以使用链式结构,代码逻辑清晰。
缺点:无法取消,有时逻辑复杂then太多,then中嵌套then,错误捕获不到正确的位置,只能通过自己的catch或者写reject(erro)的回调来捕获。
async
至此,我们再次引出我们的Async/Await语法糖
优点:以同步代码的方式去写异步,代码逻辑清晰,符合我们平时写的逻辑。缺点:因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。(这个是可以避免的)
宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的
宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出async/await不是同步了嘛,我也缕的不是很清楚,第10题那个回答挺好的
引用阮一峰老师书中一句话:“ async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。”
简单的说,先去执行后面的同步任务代码,执行完成后,也就是表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。(其实还是本轮循环promise的问题,最后的resolve属于异步,位于本轮循环的末尾。)
async1 end --> Promise then
我试了 结果一样啊 都是 async1 end --> Promise then
@sisterAn 你好,关于你的 3.async/await 我觉得不是很完备。
async function async1(){
console.log('async1 start');
// 我理解是这里的 async2 函数是返回的 Premise 对象
// 但是 Premise 其本身就是一个宏任务 不用 await 也应该是同步执行的
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
而且请看一下下面例子是否能够表明清楚
或者有什么不对的地方
// 被添加入下一次宏任务队列
setTimeout(_ => console.log(6));
async function test() {
console.log(1);
// 我的理解是
// 遇到 await 先执行其同步内容
// 让出执行线程(跳出当前任务)同时推入微任务队列 继续往下执行
// 执行完之后 回来继续执行
await new Promise((resolve, reject) => {
console.log(2);
resolve();
}).then(_ => console.log(4));
// 这里 5 没有进入微任务队列 只不是相当于被挂起了
console.log(5);
}
test();
test();
console.log(3);
// 如果执行一次 test 函数 结果 => 123456
// 如果执行两次 test 函数 结果 => 1212344556
1. setTimeout
console.log('script start') //1. 打印 script start setTimeout(function(){ console.log('settimeout') // 4. 打印 settimeout }) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数 console.log('script end') //3. 打印 script start // 输出顺序:script start->script end->settimeout2. Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
console.log('script start') let promise1 = new Promise(function (resolve) { console.log('promise1') resolve() console.log('promise1 end') }).then(function () { console.log('promise2') }) setTimeout(function(){ console.log('settimeout') }) console.log('script end') // 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout当JS主线程执行到Promise对象时,
- promise1.then() 的回调就是一个 task
- promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
- promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
- setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况
3. async/await
async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start'); async1(); console.log('script end') // 输出顺序:script start->async1 start->async2->script end->async1 endasync 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
举个例子:
async function func1() { return 1 } console.log(func1())
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。func1().then(res => { console.log(res); // 30 })
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
针对最后两步的结果,为什么是script end->async1 end?async1到async2之后没有返回到async1?可以这么理解吗:async1中对于async1 end实际上相当于async2中await返回的Promise的then回调吗?所以被归类到异步任务队列中去了?而外部的script end是一个同步的任务,所以先打印出来了
@sisterAn 你好,关于你的 3.async/await 我觉得不是很完备。
async function async1(){ console.log('async1 start'); // 我理解是这里的 async2 函数是返回的 Premise 对象 // 但是 Premise 其本身就是一个宏任务 不用 await 也应该是同步执行的 await async2(); console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start'); async1(); console.log('script end') // 输出顺序:script start->async1 start->async2->script end->async1 end而且请看一下下面例子是否能够表明清楚
或者有什么不对的地方// 被添加入下一次宏任务队列 setTimeout(_ => console.log(6)); async function test() { console.log(1); // 我的理解是 // 遇到 await 先执行其同步内容 // 让出执行线程(跳出当前任务)同时推入微任务队列 继续往下执行 // 执行完之后 回来继续执行 await new Promise((resolve, reject) => { console.log(2); resolve(); }).then(_ => console.log(4)); // 这里 5 没有进入微任务队列 只不是相当于被挂起了 console.log(5); } test(); test(); console.log(3); // 如果执行一次 test 函数 结果 => 123456 // 如果执行两次 test 函数 结果 => 1212344556
请教一下 执行两次test函数为什么不是 1212345456 呢.
js异步执行流程
Event Loop
首先js执行由三部分组成,用于执行主线程的栈,叫做execution context stack,用于存放在对象数据的heap,一般使用内存,用于存放异步事件队列的event queue
- 先说一下主线程的栈的执行流程,每个执行函数会形成一个帧,若干个帧组成了调用栈,然后依据先进后出的执行方式,从最后一个帧执行到第一个帧,前面的帧拥有后面帧的上下文,这个就是层级上下文,每个函数执行完毕之后,就弹出这一帧,销毁当前上下文,当所有的帧执行完毕之后,调用栈就被清空了,主线程此时执行完毕,也就是所谓的主线程闲置状态
- 执行过程产的数据存放在heap中,相应的上下文销毁后,内部的数据对象就会被回收
- 同时,在主线程执行过程中会产生一些异步事件,这些事件会被推入一个叫做callback queue的队列中,先进先出,异步事件的执行取决于event loop,event loop会轮询event queue,每当主调用栈处于闲置状态,事件队列中一个事件就会被推入主调用栈,继续执行步骤1、2、3;以此循环实现event loop
- 大概说一下异步事件的来源和类型,
- settimeout和setintel产生的事件回调,由定时器线程来管理定时任务,当一个定时任务被激活,一个事件就会被推入事件队列
- ajax网络请求的事件回调,由专门的网络请求线程管理
- IO操作,包括鼠标点击,键盘输入和手势操作,
- DOM更改,如requestAnimationFrame事件,Image的onload,document的onload
补充一下几乎 所有的异步事件都有专门的线程来管理,event loop只负责将队列中的事件推入调用栈,至于在什么时机推入,都是由专门的线程负责,定时器模块借助CPU时钟来定时推入事件,DOM的更改事件由渲染进程负责
宏任务与微任务
- 微任务的调度会有一个单独的 queueMicroTask,微任务事件队列依赖于宏任务的执行,当主调用栈清空,event loop检查微微任务队列,然后全部执行完毕,才能执行下一个宏任务,在此期间如果产生新的微任务,就会推入一个新的微任务队列,大概可以理解为每个宏任务有一个属于自己的微任务队列,每当本身执行完毕,还要执行 在此期间产生的 附属的微任务队列
- 最常见的微任务之一就是 promise 的then、finally、catch方法,当这些方法需要运行,就会产生一个微任务
宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的
因为其实setTimeout不是JavaScript的api,所以是在宏任务里,JavaScript的代码都是在微任务里执行的,所以执行之后才执行setTimeout的代码
传给setTimeout的回调是异步的,会放在宏任务队列里;
调用Promise的构造函数是同步的,传给Promise.then、Promise.catch、Promise.finally的回调是异步的,会放在微任务队列里;
async函数体内的第一个await表达式是宏任务,下面的都算微任务。
都是异步任务
setTimeout 属于宏任务
promise async/await 属于微任务
await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function
async function async1(){
console.log('2');
await async2();
console.log('5')
}
async function async2(){
console.log('3')
}
console.log('1');
async1();
console.log('4')
// 1 2 3 4 5
setTimout
这是运行环境代码,和js语言无关。所以chrome,Firefox,nodejs等运行环境对其实现可能不一样,但最后实现结果都一样,延迟执行一段代码。nodejs实现是通过timer,当然可以看c++源码,本质上很简单,就是死循环获取断点,实现event loop。
promise
promise就是callback函数的封装,通过js就可以自己实现一个。现在是通过c++等原生实现了一遍。相比回调函数,promise的逻辑更清晰,但最重要的是代码编写格式统一,可以实现race,all,等异步管理,rxjs就是非常好的工具。因为promise的统一格式,通过yield关键词,就可以封装出await和async,实现异步代码简化。
await和async
首先es6增加了yield关键词和generator函数,实现了编程语言上的协程。具体实现可以看babel的转换代码,v8只是先进行语义分析,然后生成相应的generator函数,逻辑和babel一样。因为其中涉及到了具体代码,所以无法写出公共函数,总之是一种语言逻辑转换,通过死循环和switch返回结果来实现代码的挂起和执行。yield加promise的语法糖就是await和async。总之,异步代码最佳结果就是await和async,一般不需要yield代码,特别复杂使用rxjs。
自己回答一波,若有错误请各位指出,互相学习!谢谢 ~ 鉴于各位上面大佬回答的,其实这题主要还是考浏览器的EVENT LOOP setTimeout属于宏任务,Promise属于微任务.而async和await其实是geneorator的语法糖,实质上最后返回的也是promise,所以我将其归为微任务(有错的,请指出,谢谢)。 而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。而Task队列中,我们又再之前提及到分:微任务和宏任务 微任务的优先级大于宏任务,所以在执行栈为空的时候,首先会去执行Micortask(微任务)队列,执行完毕后再去取Macrotask(宏任务)队列去执行栈中执行,一次执行一个,再去检查Micortask(微任务),若存在则执行Microtask,若没有则取下一个Macrotask任务继续执行,直至为空。 以上,是我对EventLoop的回答,但是我还是想在此基础上回答下关于这三个的异步不同之处。
设置超时
setTimeout的异步使用方法算是比较古老的回调函数方式,就是我们之前写Jquery的时候,ajax的最常见的使用方式,这种的好处在于用很简单的方式实现了异步的方式,从而解决了异步直肠子的问题(耗时任务,一直处于等待)。缺点:回调地狱,这是写了多年Jq的一直很恶心的地方,代码嵌套太多,牵一发而动全身。
承诺
优点:解决了回调函数的问题,可以使用链式结构,代码逻辑清晰。 缺点:无法取消,有时逻辑复杂then太多,then中嵌套then,错误捕获不到正确的位置,只能通过自己的catch或者写reject(erro)的回调来捕获。
异步
至此,我们再次引出我们的Async/Await语法糖 优点:以同步代码的方式去写异步,代码逻辑清晰,符合我们平时写的逻辑。缺点:因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。(这个是可以避免的)
??而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。 :生成主执行栈,用于执行同步任务. 执行栈都为空了还执行个什么呢?