We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
函数都是有调用栈的, 大多数程序可以看成函数调用函数不断调用函数, 比如下面这张图:
可以看到, 首先有一个 main(可以看做是入口), 然后依次是fn3()调用fn2(), 再调用fn1(), 最后调用console.log(). 一般出现错误的时候, 就会出现类似栈调用情况, 如下:
fn3()
fn2()
fn1()
console.log()
首先推荐观看这个视频What the heck is the event loop anyway?, 以及作者写了一个 event loop 可视化工具: Loupe, 不过目前这个工具支持比较有限, 不支持查看 callback queue 里面具体的 macrotask 和 microtask.
这里还是总结一下基本的 event loop 流程, 首先看这张图:
setTimeout
setTimeInterval
XMLHttpRequest
setTimeout((_) => console.log(1), 1000)
(_) => console.log(1)
setTimeout()
给两张张图片描述一下上面的流程, 其中第二张图片的代码为:
const foo = () => console.log("First"); const bar = () => setTimeout(() => console.log("Second"), 500); const baz = () => console.log("Third"); bar(); foo(); baz();
几个注意点:
所以整个执行过程是相当连贯协调, 大家分工合作, 每个函数都有自己的职责和暂时归属地, 属于哪里以及什么时候被执行都是井井有条. 避免了同步情况下, 一个无关紧要的任务被卡死, 其余任务无法被执行的现象
举一个例子:
下面的这个片段, synCb这个回调函数是不会被推入到 event queue 里面执行的, 会被浏览器当做同步任务执行, 所以打印的结果就是按照从上到下面的顺序, 依次进入堆栈输出的
synCb
console.log('start') const syncFun = function(synCb) { const v = 100 synCb(v) } syncFun(v => console.log(v)) console.log('end') // start // 100 // end
假如我们加入异步任务, 如下, 那么这里的asynCb是会作为异步任务放入 event queue 里面等主线程清空以后(也就是两个 console.log都执行完毕), 才会被推入 call stack 被调用
asynCb
console.log
console.log('start') setTimeout(function asynCb() { console.log(100) }, 0) console.log('end') // start // end // 100
总的来说在这种情况下, js 形成了两个队列: 同步任务队列和异步任务队列, 先执行同步任务队列里面的任务, 所有同步任务执行完毕以后, 再执行异步任务队列里面的任务.
例如setTimeout(callback, timer), 里面的 callback 是异步任务, 最后是会被放入到 event queue 里面的, 这里要注意一个setTimeout的点: 即里面的时间并非真正意义上的执行时间, 考虑如下代码:
setTimeout(callback, timer)
setTimeout(() => { console.log(8) }, 5000) setTimeout(() => { console.log(9) }, 5000) setTimeout(() => { console.log(10) }, 5000) // 8 // 9 // 10
上述代码最后会依次打印 8, 9, 10. 这是由于根据执行顺序会依次放入到 event queue 里面, 也就是说, 打印 8 的回调会被先放入到异步任务队列里面, 然后是 9, 10.
所以这里的时间并非指的是这个函数的执行时间, 而指代的是, 异步回调函数几秒后会被放入到任务队列
promise中, .then()里面的为异步回调
promise
.then()
假设有如下代码:
console.log('start') new Promise((resolve, reject) => { resolve(1) console.log('middle') }).then(v => console.log(v)) console.log('end') // start // middle // end // 1
上面代码中, v => console.log(v)是异步回调, 注意, new Promise在实例化的过程中所执行的代码都是同步进行的, 所以console.log('middle')会同步执行
v => console.log(v)
new Promise
console.log('middle')
async/await 本质上还是基于 Promise 的一些封装, async函数在await 之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise时传入的代码,await 之后的所有代码都是在Promise.then中的回调
async/await
Promise
async
await
Promise.then
console.log('start') async function main() { console.log('test') const v = await Promise.resolve(1) console.log(v) } main() console.log('end') // start // test // end // 1
使用request, 写一个getUser函数, 有基本以下写法:
getUser
回调写法:
const getUser = function(callback) { request('http://www.example.com', function(err, res, body){ callback(body) }) } getUser(v => console.log(v))
Promise 写法:
const getUser = function() { return new Promise((resolve, reject) => { request('http://www.example.com', function(err, res, body){ if (err) { return reject(err) } resolve(body) }) }) } getUser() .then(v => console.log(v)) .catch(err => console.error(err))
async/await 写法:
const getUser = function() { return new Promise((resolve, reject) => { request('http://www.example.com', function(err, res, body){ if (err) { return reject(err) } resolve(body) }) }) } (async function() { try { const v = await getUser() console.log(v) } catch(e) { console.error(e) } })()
上面提到的 event queue 里的异步任务还可以细分为: macrotask(宏任务), microtask(微任务). 这里注意, 现在标准称呼可以认为是 tasks 和 jobs, 但本文还是以宏任务和微任务来代指
宏任务:
setInterval
微任务:
process.nextTick
当然上面的任务其实均指的是其中的回调函数, 而上面的 api 前面也提过, 充当调度者的作用
有几个概念需要注意一下:
promise.then(callback)
callback
resolved
rejected
setTimeout(callback, t)
t
关于宏任务和微任务的模型可以看成如下图的形式:
有几个重要的概念:
总结:
异步任务模型伪代码可以模拟成如下:
// 第一次 loop [ ['宏任务 1', '微任务 1.1', ' 微任务 1.2'], ['宏任务 2'], ['宏任务 3'], ] // 第二次 loop [ // ['宏任务 1', '微任务 1.1', ' 微任务 1.2'], 执行完毕被清空 ['宏任务 2', '微任务 2.1'], ['宏任务 3'], ] // 第三次 loop [ // ['宏任务 1', '微任务 1.1', ' 微任务 1.2'], 执行完毕被清空 // ['宏任务 2'], 执行完毕被清空 ['宏任务 3'], ]
示例 1:
console.log('start') setTimeout(function() { console.log('timeout') }, 0) new Promise(function(resolve) { console.log('promise') resolve() }).then(function() { console.log('promise resolved') }) console.log('end') // start // promise // end // promise resolved // timeout
分析:
resolve
.then
示例 2:
console.log('start') setTimeout(function () { console.log('event loop2, macrotask') new Promise(function (resolve) { console.log('event loop2, macrotask continue') resolve() }).then(function () { console.log('event loop2, microtask1') }) }, 0) new Promise(function (resolve) { console.log('middle') resolve() }).then(function () { console.log('event loop1, microtask1') setTimeout(function () { console.log('event loop3, macrotask') }) }) console.log('end') // start // middle // end // event loop1, microtask1 // event loop2, macrotask // event loop2, macrotask continue // event loop2, microtask1 // event loop3, macrotask
前两天在知乎看到的一个问题: JavaScript的DOM事件回调不是宏任务吗,为什么在本次微任务队列触发
console.log('本轮任务'); new Promise((resolve, reject) => { resolve(3) }).then(() => { console.log('本轮微任务'); }) document.getElementById('div').addEventListener('click', () => { console.log('click'); }) document.getElementById('div').click() // 本轮任务 // click // 本轮微任务
这里注意: click()和dispatchEvent()等人工合成事件是同步触发的, 其回调并非会被放入宏任务队列中, 而是直接作为同步任务执行, 具体答案可以参考这个回答 和补充
click()
dispatchEvent()
The text was updated successfully, but these errors were encountered:
No branches or pull requests
简单聊一聊浏览器下的 event-loop, 微任务与宏任务
函数调用栈
函数都是有调用栈的, 大多数程序可以看成函数调用函数不断调用函数, 比如下面这张图:
可以看到, 首先有一个 main(可以看做是入口), 然后依次是
fn3()
调用fn2()
, 再调用fn1()
, 最后调用console.log()
. 一般出现错误的时候, 就会出现类似栈调用情况, 如下:event loop
首先推荐观看这个视频What the heck is the event loop anyway?, 以及作者写了一个 event loop 可视化工具: Loupe, 不过目前这个工具支持比较有限, 不支持查看 callback queue 里面具体的 macrotask 和 microtask.
这里还是总结一下基本的 event loop 流程, 首先看这张图:
setTimeout
,setTimeInterval
,XMLHttpRequest
等等setTimeout((_) => console.log(1), 1000)
, 这里绿色部分所做的是:(_) => console.log(1)
这个回调放到 Event Queue(事件队列, 蓝色部分) 里面, 这里注意: 并非是把setTimeout()
整个函数放入到事件队列里面, 仅放入其中的回调函数部分给两张张图片描述一下上面的流程, 其中第二张图片的代码为:
几个注意点:
所以整个执行过程是相当连贯协调, 大家分工合作, 每个函数都有自己的职责和暂时归属地, 属于哪里以及什么时候被执行都是井井有条. 避免了同步情况下, 一个无关紧要的任务被卡死, 其余任务无法被执行的现象
异步任务和同步任务
举一个例子:
下面的这个片段,
synCb
这个回调函数是不会被推入到 event queue 里面执行的, 会被浏览器当做同步任务执行, 所以打印的结果就是按照从上到下面的顺序, 依次进入堆栈输出的假如我们加入异步任务, 如下, 那么这里的
asynCb
是会作为异步任务放入 event queue 里面等主线程清空以后(也就是两个console.log
都执行完毕), 才会被推入 call stack 被调用总的来说在这种情况下, js 形成了两个队列: 同步任务队列和异步任务队列, 先执行同步任务队列里面的任务, 所有同步任务执行完毕以后, 再执行异步任务队列里面的任务.
异步回调
普通函数回调
例如
setTimeout(callback, timer)
, 里面的 callback 是异步任务, 最后是会被放入到 event queue 里面的, 这里要注意一个setTimeout
的点: 即里面的时间并非真正意义上的执行时间, 考虑如下代码:上述代码最后会依次打印 8, 9, 10. 这是由于根据执行顺序会依次放入到 event queue 里面, 也就是说, 打印 8 的回调会被先放入到异步任务队列里面, 然后是 9, 10.
所以这里的时间并非指的是这个函数的执行时间, 而指代的是, 异步回调函数几秒后会被放入到任务队列
Promise 和 async/await
Promise
promise
中,.then()
里面的为异步回调假设有如下代码:
上面代码中,
v => console.log(v)
是异步回调, 注意,new Promise
在实例化的过程中所执行的代码都是同步进行的, 所以console.log('middle')
会同步执行async/await
async/await
本质上还是基于Promise
的一些封装,async
函数在await
之前的代码都是同步执行的,可以理解为await
之前的代码属于new Promise
时传入的代码,await
之后的所有代码都是在Promise.then
中的回调写法总结
使用request, 写一个
getUser
函数, 有基本以下写法:回调写法:
Promise 写法:
async/await 写法:
宏任务与微任务
上面提到的 event queue 里的异步任务还可以细分为: macrotask(宏任务), microtask(微任务). 这里注意, 现在标准称呼可以认为是 tasks 和 jobs, 但本文还是以宏任务和微任务来代指
宏任务:
setTimeout
,setInterval
微任务:
Promise
process.nextTick
当然上面的任务其实均指的是其中的回调函数, 而上面的 api 前面也提过, 充当调度者的作用
有几个概念需要注意一下:
promise.then(callback)
里面的callback
是一个微任务, 且会被推入当前的微任务队列, 当且仅当该promise
状态变更为resolved
或者rejected
, 否则不被推入任务队列setTimeout(callback, t)
的callback
是一个宏任务, 会被推入当前的宏任务队列中, 即使t
为 0模型
关于宏任务和微任务的模型可以看成如下图的形式:
有几个重要的概念:
总结:
异步任务模型伪代码可以模拟成如下:
示例
示例 1:
分析:
setTimeout
,将回调函数放入宏任务队列,等待执行new Promise
,其回调函数并不会被放入其他任务队列,因此会同步地执行,打印promise
,但是当resolve
后,.then
会把其内部的回调函数放入微任务队列示例 2:
分析:
setTimeout
, 回调放入宏任务队列中.then()
将回调放当前入微任务队列中setTimeout
, 将其中回调放入宏任务队列中一个相关问题
前两天在知乎看到的一个问题: JavaScript的DOM事件回调不是宏任务吗,为什么在本次微任务队列触发
这里注意:
click()
和dispatchEvent()
等人工合成事件是同步触发的, 其回调并非会被放入宏任务队列中, 而是直接作为同步任务执行, 具体答案可以参考这个回答 和补充参考
The text was updated successfully, but these errors were encountered: